Disclaimer: This is not an official Google product.
A small Java 8 utilities library (javadoc), with 0 deps (Proto, BigQuery, Guava addons are in separate artifacts).
- Stream utilities (BiStream, MoreStreams, Iteration, Guava Addons):
histogram = zip(times, counts).toMap();
- Optionals provides extra utilities for Optional:
optional(id.length() > 0, id)
- Substring fluent string manipulation library:
String user = first('@').toEnd().removeFrom(email);
- StringFormat extracts structured data from string:
new StringFormat("{yyyy}/{mm}/{dd}").parse(dateStr, (yyyy, mm, dd) -> ...)
- DateTimeFormats creates
DateTimeFormatter
from an (any) example date time string (golang style but without magic dates):DateTimeFormatter formatter = DateTimeFormats.formatOf("Tue, 10 Jan 2023 10:00:00.123 America/Los_Angeles")
- SafeQuery and GoogleSql for injection-safe SQL templating.
- Parallelizer An Executor-friendly, interruptible alternative to parallel streams.
- Graph utilities (Walker, ShortestPath)
- Google Protobuf Java 8 Utilities
- BigQuery ParameterizedQuery
Add the following to pom.xml:
<dependency>
<groupId>com.google.mug</groupId>
<artifactId>mug</artifactId>
<version>7.1</version>
</dependency>
Add mug-errorprone
to your annotationProcessorPaths:
<build>
<pluginManagement>
<plugins>
<plugin>
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
<annotationProcessorPaths>
<path>
<groupId>com.google.errorprone</groupId>
<artifactId>error_prone_core</artifactId>
<version>2.23.0</version>
</path>
<path>
<groupId>com.google.mug</groupId>
<artifactId>mug-errorprone</artifactId>
<version>7.1</version>
</path>
</annotationProcessorPaths>
</configuration>
</plugin>
</plugins>
</pluginManagement>
</build>
Protobuf utils:
<dependency>
<groupId>com.google.mug</groupId>
<artifactId>mug-protobuf</artifactId>
<version>7.1</version>
</dependency>
Guava add-ons (with SafeQuery
and GoogleSql
):
<dependency>
<groupId>com.google.mug</groupId>
<artifactId>mug-guava</artifactId>
<version>7.1</version>
</dependency>
Add to build.gradle:
implementation 'com.google.mug:mug:7.1'
implementation 'com.google.mug:mug-guava:7.1'
implementation 'com.google.mug:mug-protobuf:7.1'
BiStream streams pairs of objects.
This class closely mirrors JDK Stream
API (the few extra methods of "its own" are very straight-forward). If you are familiar with Jdk stream, learning curve is minimal.
Example 1: to concatenate Map
s:
import static com.google.mu.util.stream.BiStream.concat;
Map<AccountId, Account> allAccounts = concat(primaryAccouunts, secondaryAccounts).toMap();
Example 2: to combine two streams:
BiStream.zip(requests, responses)
.mapToObj(RequestAndResponseLog::new);
Example 3: to build a Map fluently:
Map<DoctorId, Patient> patientsByDoctorId = BiStream.zip(doctors, patients)
.filter((doctor, patient) -> patient.likes(doctor))
.mapKeys(Doctor::getId)
.collect(toMap());
Example 4: to build Guava ImmutableListMultimap fluently:
ImmutableListMultimap<ZipCode, Address> addressesByZipCode = BiStream.from(addresses)
.mapKeys(Address::getZipCode)
.collect(ImmutableListMultimap::toImmutableListMultimap);
Example 5: to
a Map
into sub-maps:
import static com.google.mu.util.stream.BiCollectors.groupingBy;
Map<Address, PhoneNumber> phonebooks = ...;
Map<State, Map<Address, PhoneNumber>> statePhonebooks = BiStream.from(phonebooks)
.collect(groupingBy(Address::state, Collectors::toMap))
.toMap();
Example 6: to merge Map
entries:
import static com.google.mu.util.stream.BiCollectors.toMap;
import static com.google.mu.util.stream.MoreCollectors.flatteningMaps;
Map<Account, Money> totalPayouts = projects.stream()
.map(Project::payments) // Stream<Map<Account, Money>>
.collect(flatteningMaps(toMap(Money::add)));
Example 7: to apply grouping over Map
entries:
import static com.google.mu.util.stream.BiCollectors.toMap;
import static com.google.mu.util.stream.MoreCollectors.flatteningMaps;
import static java.util.stream.Collectors.summingInt;
Map<EmployeeId, Integer> workerHours = projects.stream()
.map(Project::getTaskAssignments) // Stream<Map<Employee, Task>>
.collect(flatteningMaps(toMap(summingInt(Task::hours))));
Example 8: to turn a Collection<Pair<K, V>>
to BiStream<K, V>
:
BiStream<K, V> stream = RiStream.from(pairs, Pair::getKey, Pair::getValue);
Q: Why not Map<Foo, Bar>
or Multimap<Foo, Bar>
?
A: Sometimes Foo and Bar are just an arbitrary pair of objects, with no key-value relationship. Or you may not trust Foo#equals()
and hashCode()
. Instead, drop-in replace your Stream<Pair<Foo, Bar>>
/List<Pair<Foo, Bar>>
with BiStream<Foo, Bar>
/BiCollection<Foo, Bar>
to get better readability.
Q: Why not Stream<FooAndBar>
?
A: When you already have a proper domain object, sure. But you might find it cumbersome to define a bunch of FooAndBar, PatioChairAndKitchenSink one-off classes especially if the relationship between the two types is only relevant in the local code context.
Q: Why not Stream<Pair<Foo, Bar>>
?
A: It's distracting to read code littered with opaque method names like getFirst()
and getSecond()
.
Extracts structured data from string:
new StringFormat("/users/{user}/.{hidden_file_name}")
.parse(filePath, (user, fileName) -> ...);
new StringFormat("{hour}:{minute}:{second}.{millis}")
.parse(“10:26:30.748”, (hour, minute, second, millis) -> ...);
An ErrorProne check is in place to check that the number of lambda parameters and the parameter names match the format string.
This allows you to define StringFormat
objects as private class constant, and safely use them
many lines away.
Example 1: strip off a prefix if existent:
String httpStripped = Substring.prefix("http://").removeFrom(uri);
Example 2: strip off any scheme prefix from a uri:
String schemeStripped = Substring.upToIncluding(first("://")).removeFrom(uri);
Example 3: split a string in the format of "name=value" into name
and value
:
Substring.first('=').split("name=value").map((name, value) -> ...);
Example 4: replace trailing "//" with "/" :
Substring.suffix("//").replaceFrom(path, "/");
Example 5: strip off the suffix starting with a dash (-) character :
last('-').toEnd().removeFrom(str);
Example 6: extract a substring using regex :
String quoted = Substring.first(Pattern.compile("'(.*?)'"), 1)
.from(str)
.orElseThrow(...);
Example 7: find the substring between the first and last curly braces ({) :
String body = Substring.between(first('{'), last('}'))
.from(source)
.orElseThrow(...);
Example 1: to combine two Optional instances into a single one:
Optional<Couple> couple = Optionals.both(optionalHusband, optionalWife).map(Couple::new);
Example 2: to run code when two Optional instances are both present:
Optionals.both(findTeacher(), findStudent()).ifPresent(Teacher::teach);
Example 3: or else run a fallback code block:
static import com.google.mu.util.Optionals.ifPresent;
Optional<Teacher> teacher = findTeacher(...);
Optional<Student> student = findStudent(...);
ifPresent(teacher, student, Teacher::teach) // teach if both present
.or(() -> ifPresent(teacher, Teacher::workOut)) // teacher work out if present
.or(() -> ifPresent(student, Student::doHomework)) // student do homework if present
.orElse(() -> log("no teacher. no student")); // or else log
Example 4: wrap a value in Optional if it exists:
static import com.google.mu.util.Optionals.optionally;
Optional<String> id = optionally(request.hasId(), request::getId);
All Optionals utilites propagate checked exception from the the lambda/method references.
Example 1: to group consecutive elements in a stream:
List<StockPrice> pricesOrderedByTime = ...;
List<List<StockPrice>> priceSequences =
MoreStreams.groupConsecutive(
pricesOrderedByTime.stream(), (p1, p2) -> closeEnough(p1, p2), toList())
.collect(toList());
Example 2: to iterate over Stream
s in the presence of checked exceptions or control flow:
The Stream
API provides forEach()
to iterate over a stream, if you don't have to throw checked exceptions.
When checked exception is in the way, or if you need control flow (continue
, return
etc.), iterateThrough()
and iterateOnce()
can help. The following code uses iterateThrough()
to write objects into an ObjectOutputStream
, with IOException
propagated:
Stream<?> stream = ...;
ObjectOutput out = ...;
iterateThrough(stream, out::writeObject);
with control flow:
for (Object obj : iterateOnce(stream)) {
if (...) continue;
else if (...) return;
out.writeObject(obj);
}
Example 3: to merge maps:
interface Page {
Map<Day, Long> getTrafficHistogram();
}
List<Page> pages = ...;
// Merge traffic histogram across all pages of the web site
Map<Day, Long> siteTrafficHistogram = pages.stream()
.map(Page::getTrafficHistogram)
.collect(flatteningMaps(groupingBy(day -> day, Long::sum)))
.toMap();