Create a Map based on a Stream and Joining a stream to a single String


Create a Map based on a Stream

Simple case without duplicate keys

Stream<String> characters = Stream.of("A", "B", "C");
 
Map<Integer, String> map = characters
     .collect(Collectors.toMap(element -> element.hashCode(), element -> element));
// map = {65=A, 66=B, 67=C}

To make things more declarative, we can use static method in Function interface - Function.identity(). We can replace this lambda element -> element with Function.identity().

Case where there might be duplicate keys

The javadoc for Collectors.toMap states:

  • If the mapped keys contains duplicates (according to Object.equals(Object)), an IllegalStateException is thrown when the collection operation is performed. If the mapped keys may have duplicates, use toMap(Function, Function, BinaryOperator) instead.
Stream<String> characters = Stream.of("A", "B", "B", "C");
 
Map<Integer, String> map = characters
	.collect(Collectors.toMap(
		element -> element.hashCode(),
		element -> element,
		(existingVal, newVal) -> (existingVal + newVal)));
 
// map = {65=A, 66=BB, 67=C}

The BinaryOperator passed to Collectors.toMap(...) generates the value to be stored in the case of a collision. It can

  • return the old value, so that the first value in the stream takes precedence,
  • return the new value, so that the last value in the stream takes precedence, or
  • combine the old and new values

Grouping by value

You can use Collectors.groupingBy when you need to perform the equivalent of a database cascaded "group by" operation. To illustrate, the following creates a map in which people's names are mapped to surnames:

import java.util.*;
import java.util.stream.*;
import static java.util.stream.Collectors.*;
 
class Person {
	final String name, surname;
	public Person(String n, String s){
		this.name = n;
		this.surname = s;
	}
	public String getName(){ return name; }
	public String getSurname(){ return surname; }
}
 
class Sample{
 
	public static void main(String[] args) {
	    List<Person> people = Arrays.asList(
		    new Person("Sam", "Rossi"),
		    new Person("Sam", "Verdi"),
		    new Person("John", "Bianchi"),
		    new Person("John", "Rossi"),
		    new Person("John", "Verdi")
    	);
 
	    Map<String, List<String>> map = people.stream()
            .collect(
                // function mapping input elements to keys
                groupingBy(Person::getName, 
                // function mapping input elements to values,
                // how to store values
                mapping(Person::getSurname, toList()))
            );
        System.out.println(map);
	}
}

Joining a stream to a single String

A use case that comes across frequently, is creating a String from a stream, where the stream-items are separated by a certain character. The Collectors.joining() method can be used for this, like in the following example:

Stream<String> fruitStream = Stream.of("apple", "banana", "pear", "kiwi", "orange");
 
String result = fruitStream.filter(s -> s.contains("a"))
	.map(String::toUpperCase)
	.sorted()
	.collect(Collectors.joining(", "));
 
System.out.println(result);

Output:

APPLE, BANANA, ORANGE, PEAR

The Collectors.joining() method can also cater for pre- and postfixes:

String result = fruitStream.filter(s -> s.contains("e"))
	.map(String::toUpperCase)
	.sorted()
	.collect(Collectors.joining(", ", "Fruits: ", "."));
 
System.out.println(result);

Output:

Fruits: APPLE, ORANGE, PEAR.

Sort Using Stream

List<String> data = new ArrayList<>();
data.add("Sydney");
data.add("London");
data.add("New York");
data.add("Amsterdam");
data.add("Mumbai");
data.add("California");
 
System.out.println(data);
 
List<String> sortedData = data.stream().sorted().collect(Collectors.toList());
 
System.out.println(sortedData);

Output:

[Sydney, London, New York, Amsterdam, Mumbai, California]
[Amsterdam, California, London, Mumbai, New York, Sydney]

It's also possible to use different comparison mechanism as there is a overloaded sorted version which takes a comparator as its argument.

Also, you can use a lambda expression for sorting:

List<String> sortedData2 = data.stream().sorted((s1,s2) ->
      s2.compareTo(s1)).collect(Collectors.toList());

This would output

[Sydney, New York, Mumbai, London, California, Amsterdam]

You can use Comparator.reverseOrder() to have a comparator that imposes the reverse of the natural ordering.

List<String> reverseSortedData = data.stream().sorted(Comparator.reverseOrder()).collect(Collectors.toList());

Basic Programs