Skip to content

Joiner Explained

Ben Yu edited this page Nov 15, 2021 · 11 revisions

Why Joiner when Guava already has Joiner?

Starting from Java 8, the JDK joining() collector has more or less superseded Guava Joiner for the primary intended use case. Joiner is slightly more convenient in that it can join Object while JDK joining() can only join strings. There are also auxiliary functionalities in Joiner that aren't directly provided by JDK, such as MapJoiner, skipNulls() etc. However those alone no longer satisfy the high barrier of entry for Guava inclusion.

Guava is unlikely to deprecate Joiner, but it's unlikely to evolve either.

What does Mug Joiner provide?

Mug Joiner is implemented as a first-class Collector so you can directly use it in streams: stream.collect(Joiner.on(',')).

Similar to Guava Joiner, it can join Object and provides skipNulls(). In addition, we provide fluent methods such as between(), skipEmpties() etc.

From what we've learned internally, the JDK joining(String, String, String) collector that supports prefix and suffix is prone to misuse. Some may intuitively expect to call it like joining("{", ",", "}") following the encounter order of delimiters; except JDK's ordering is joining(",", "{", "}"). Using between(), it can be expressed fluently with no ambiguity: Joiner.on(',').between('{', '}').

The binary join(Object, Object) can be used in a BiStream to combine the pair, as in:

BiStream.zip(ids, names)
    .mapToObj(Joiner.on(':')::join)

While you can do the same with Guava's Joiner too, the dedicated binary join() is more efficient as it doesn't create temporary arrays and lists.

Should you mix Guava Joiner with Mug Joiner?

We expect Mug Joiner to provide all functionalities you need. You won't need to use Guava Joiner any more.

What about MapJoiner?

When you have a Map, Multimap or BiStream, joining the entries is as easy as:

// {k1=v1, k2=v2, ...}
BiStream.from(map)
    .mapToObj(Joiner.on('=')::join)
    .collect(Joiner.on(", ").between('{', '}'));

It's more readable than MapJoiner because you can provide the delimiters in a consistent ordering: from inside out. That is, first you set the delimiter for each individual pair, then the delimiter between pairs, and finally the prefix and suffix of the entire string.

That said, if you truly want to combine them together, for example in a reusable BiCollector, you can do so easily as in:

static BiCollector<Object, Object, String> toMapString() {
  return BiCollectors.mapping(Joiner.on('=')::join, Joiner.on(", ").between('{', '}'));
}