Joiner in Google Guava

Introduction

Google Guava is a Java library belonging to the Guava project of Google. It provides helpful methods dealing with collections, caching, concurrency, string processing, I/O, etc. These libraries help us to simplify our code. In an earlier post on the String Utilities in Google Guava, we looked at a couple of String Utility classes – the Strings and the Splitter. In this post, we will look at the Joiner Utility class in the Google Guava library. Hence, we can think of this post as the String Utilities in Google Guava part II.

For understanding on how to depend on the Google Guava library and how to include it in your project refer to the Getting Google Guava section from my previous post. 

Google Guava Joiner

A Joiner class is symmetric to a Splitter class. A Splitter is used to split a string into non-overlapping substrings. A Joiner in Google Guava is used to join various strings into one. From the javadoc, a Joiner is an object which joins pieces of text specified as an array, Iterable, varargs or even aMap with a separator. It either appends the results to an Appendable or returns them as a String.

The below code snippet shows how we can join three pieces of string by a comma to form the final concatenated String.

Joiner commaCharJoiner = Joiner.on(',');
System.out.println(commaCharJoiner.join("text1", "text2", "text3")); //text1,text,text3

Creating Joiner instance

A Joiner can be created by passing a delimiter – this can either be a character or a String. We just saw using a character as a delimiter.

Joiner commaJoiner = Joiner.on(", "); //note the space after comma
System.out.println(commaJoiner.join("text1", "text2", "text3")); //text1, text2, text3

The appendTo methods

The Joiner provides two classes of appendTo methods – one taking a StringBuilder and one taking an Appendable.  We will look at the ones taking a StringBuilder. Similar overloaded methods exist that take any Appendable in place of a StringBuilder.Note: A StringBuilder is an Appendable. The reason for providing a explicit methods accepting a StringBuilder is that the append methods on StringBuilder do not throw an IOException whereas all append methods on a general Appendable throw an IOException.

There are four overloads that enable passing list of string in various forms – Iterable, Iterator, varargs and as an Object array.

Iterable
StringBuilder appendTo(StringBuilder builder, Iterable<?> parts) 
 
Iterator
StringBuilder appendTo(StringBuilder builder, Iterator<?> parts) 
 
Object[]
StringBuilder appendTo(StringBuilder builder, Object[] parts)
 
Varargs
StringBuilder appendTo(StringBuilder builder, @Nullable Object first, 
    @Nullable Object second, Object... rest)
 
List<String> input = Arrays.asList("one", "two", "three");

//Using an Iterable
StringBuilder stringBuilder = new StringBuilder();
commaJoiner.appendTo(stringBuilder, input);
System.out.println(stringBuilder.toString()); //one, two, three

//Using an Iterator
stringBuilder = new StringBuilder();
commaJoiner.appendTo(stringBuilder, input.iterator());
System.out.println(stringBuilder.toString()); //one, two, three

//Using an Object[]
stringBuilder = new StringBuilder();
commaJoiner.appendTo(stringBuilder, new Object[] {"one", "two", "three"});
System.out.println(stringBuilder.toString()); //one, two, three

//Using varargs
stringBuilder = new StringBuilder();
commaJoiner.appendTo(stringBuilder, "one", "two", "three");
System.out.println(stringBuilder.toString()); //one, two, three

Note: Even though each of the above methods return a StringBuilder, it is not a new StringBuilder instance. It modifies or mutates the StringBuilder that we pass. A return type of the same type is there to enable method chaining.

The join method

If the intention is to concatenate the passed list of strings into a String, we don’t have to use a StringBuilder. There are four overloaded methods (join) that returns a String directly.

//Using an iterable
System.out.println(commaJoiner.join(input)); //one, two, three

//Using an Iterator
System.out.println(commaJoiner.join(input.iterator())); //one, two, three

//Using an Object[]
System.out.println(commaJoiner.join(new Object[] {"one", "two", "three"})); //one, two, three
       
//Using varargs
System.out.println(commaJoiner.join("one", "two", "three")); //one, two, three

Modifier methods

Similar to how a Splitter class had a few modifier methods (like trimResults, omitEmptyStrings and limit), a Joiner has a couple of them – skipNulls and useForNull. These are necessary when dealing with nulls in the input.

If there is a list of Strings that can potentially have nulls, calling any of the methods that join (join or appendTo) can throw a NullPointerException. To avoid this, we can either ask it to skip the nulls (using skipNulls) or use a substitute string in place of null (useForNull).

List<String> inputWithNulls = Arrays.asList("one", "two", null, "four");
Joiner commaJoinerSkippingNulls = Joiner.on(", ")
                .skipNulls();
System.out.println(commaJoinerSkippingNulls.join(inputWithNulls)); //one, two, four

Joiner commaJoinerHandlingNulls = Joiner.on(", ")
                .useForNull("NULL");
System.out.println(commaJoinerHandlingNulls.join(inputWithNulls)); //one, two, NULL, four

Important note: The Joiner instances are immutable (like Splitter). Thus calling any of the modifier methods returns a new Joiner and does not modify/mutate the original joiner.

MapJoiner

As we saw a Joiner joins Iterables and arrays. Similarly, a MapJoiner joins map entries. We can obtain a MapJoined by using the useKeyValueSeparator method.

Joiner.MapJoiner mapJoiner = commaJoiner.withKeyValueSeparator("=");
Map<String, String> map = new HashMap<>();
map.put("key1", "value1");
map.put("key2", "value2");
System.out.println(mapJoiner.join(map)); //key1=value1, key2=value2

//Handling nulls
map.put("key3", null);

Joiner.MapJoiner mapJoinerHandlingNulls = commaJoiner.withKeyValueSeparator("=")
                .useForNull("NULL");
System.out.println(mapJoinerHandlingNulls.join(map)); //key1=value1, key2=value2, key3=NULL

Note: There are join methods in a MapJoiner that accepts an Iterable or an Iterator of Map entries (Iterable<? extends Entry<?, ?>> or Iterator <? extends Entry<?, ?>> or). But these APIs are maked as @Beta. This means that the API contract can change or the method might even be removed in the future.

Conclusion

In this post, we had a look at the Joiner class in Google Guava. It can help us join a collection of strings into one string. First, we learnt about the various overloaded appendTo methods that work on an Appendable or a StringBuilder. Second, we looked at the join methods that join the strings and return a String. Finally, we learnt about the MapJoiner which is a handy utility to convert a map into a string.
Head over to the String Utilities in Google Guava if you haven’t read it yet.

References

Google Guava Strings Explained

Leave a Reply