Learn about Collectors.joining with examples.

1. Introduction

This article is the 3rd article in the Collectors series.

  1. Read about the Collector interface.
  2. Read about Collectors class’s methods like toList, toUnmodifiableList, toSet, toUnmodifiableSet, toMap, toUnmodifiableMap, toConcurrentMap, toCollection. This article has a plethora of examples to ease your learning.

In this article, we will learn about 3 Collector methods that return a String. Consider it a toString method for Stream interface.

2. Content

Below are 3 methods that we will learn in this article:

public static Collector<CharSequence, ?, String> joining()

public static 
Collector<CharSequence, ?, String> joining(CharSequence delimiter)

public static 
Collector<CharSequence, ?, String> joining(
			CharSequence delimiter, 
			CharSequence prefix, 
			CharSequence suffix)

I will explain all methods without the Collector methods too, i.e. methods that have the same behavior as the above Collector methods. In doing that, you will know how power Collector methods really are.

Our use-case for this article is: Get distinct item names and print them to console.

3. public static
Collector<CharSequence, ?, String> joining()

joining() method is used to concatenate all the objects in Stream and returns String.

Below is the code that uses Stream APIs and joining() method.

@Test
public void joining() {
	List<Transaction> txns = Transactions.getDataSet();

	String items = 
			txns.stream()
				.map(Transaction::invoice)
				.flatMap(inv -> inv.items().stream())
				.map(Item::itemName)
				.distinct()
				.collect(Collectors.joining());

	System.out.println(items);

}
Output: 
PS 4ShirtCandyToy

You can achieve the same result without using Stream API and joining() method. Below method, joiningWithoutStream() does the same thing. It shows that without the Stream APIs and joining method; we have to write a lot of code and maintain it.

@Test
public void joiningWithoutStream() {
		
	List<Transaction> txns = Transactions.getDataSet();
		
	StringBuilder sb = new StringBuilder();
		
	Set<String> itemsNames = new HashSet<>();
		
	for (Transaction transaction : txns) {
		Invoice invoice = transaction.invoice();
		for (Item item : invoice.items()) {
			itemsNames.add(item.itemName());
		}
	}

	for (String itemName : itemsNames) {
		sb.append(itemName);
	}

	System.out.println(sb.toString());
}

Output: 
PS 4ShirtCandyToy

4. public static
Collector<CharSequence, ?, String>
joining(CharSequence delimiter)

joining(CharSequence delimiter) method accepts a delimiter. A delimiter is a CharSequence that separates two elements.

In a previous example, we saw output as PS 4ShirtCandyToy. If we use delimiter “, ” then the output becomes 

PS 4, Shirt, Candy, Toy.

@Test
public void joiningWithDelimiter() {
	List<Transaction> txns = Transactions.getDataSet();

	String items = 
			txns.stream()
				.map(Transaction::invoice)
				.flatMap(inv -> inv.items().stream())
				.map(Item::itemName)
				.distinct()
				.collect(Collectors.joining(", "));

	System.out.println(items);

}
Output: 
PS 4, Shirt, Candy, Toy

Let us see how to achieve this functionality without Stream and joining(delimiter) method.

@Test
public void joiningWithDelimiterWithoutStream() {
	List<Transaction> txns = Transactions.getDataSet();
		
	final String delimiter = ", ";
		
	StringBuilder sb = new StringBuilder();
		
	Set<String> itemsNames = new HashSet<>();
		
	for (Transaction transaction : txns) {
		Invoice invoice = transaction.invoice();
		for (Item item : invoice.items()) {
			itemsNames.add(item.itemName());
		}
	}
		
	Iterator<String> iterator = itemsNames.iterator();
	if (iterator.hasNext()) {
		sb.append(iterator.next());
		while (iterator.hasNext()) {
			sb.append(delimiter);
			sb.append(iterator.next());
		}
	}
		
	System.out.println(sb.toString());

}

Output: 
PS 4, Shirt, Candy, Toy

5. public static
Collector<CharSequence, ?, String>
joining(CharSequence delimiter, CharSequence prefix, CharSequence suffix)

joining(delimiter, prefix, suffix) method is used to provide prefix and suffix to a result String. For example, if the strings are a, b, c, d prefix= { and suffix = } then output is {a, b, c, d}.

@Test
public void joiningWithPrefixDelimiterSuffix() {
	List<Transaction> txns = Transactions.getDataSet();

	String items = 
			txns.stream()
				.map(Transaction::invoice)
				.flatMap(inv -> inv.items().stream())
				.map(Item::itemName)
				.distinct()
				.collect(Collectors.joining(", ", "{", "}"));
		
	System.out.println(items);

}

Output: 
{Shirt, Toy, Candy, PS 4}

We can achieve the same without using the Stream API and joining() method.

@Test
public void joiningWithPrefixDelimiterSuffixWithoutStream() {
	List<Transaction> txns = Transactions.getDataSet();
		
	final String delimiter = ", ";
	final String prefix = "{";
	final String suffix= "}";
		
	StringBuilder sb = new StringBuilder();
	sb.append(prefix);

	Set<String> itemsNames = new HashSet<>();
		
	for (Transaction transaction : txns) {
		Invoice invoice = transaction.invoice();
		for (Item item : invoice.items()) {
			itemsNames.add(item.itemName());
		}
	}
		
	Iterator<String> iterator = itemsNames.iterator();
	if(iterator.hasNext()) {
		sb.append(iterator.next());
		while(iterator.hasNext()) {
			sb.append(delimiter);
			sb.append(iterator.next());
		}
	}
		
	sb.append(suffix);
		
	System.out.println(sb.toString());

}

Output: 
{Shirt, Toy, Candy, PS 4}

6. Conclusion

In this article we saw how to convert Stream to String. All 3 methods had 2 examples. First example shows the working of joining() method, whereas the second example shows how to achieve the same without using the Stream API and joining().

Leave a Reply

Your email address will not be published. Required fields are marked *