This is the Java Stream API, introduced in Java 8 - not the I/O package streams.

Not a tutorial - just some examples.

Filter Map Reduce

Example (taken from the package documentation):

Here we use widgets, a Collection<Widget>, as a source for a stream, and then perform a filter-map-reduce on the stream to obtain the sum of the weights of the red widgets:

1
2
3
4
int sum = widgets.stream()
        .filter(b -> b.getColor() == RED)
        .mapToInt(b -> b.getWeight())
        .sum();

Group by a Key

Convert a vararg list of Map<Integer, MyObject>s to Map<Integer, Set<MyObject>>:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
import java.util.Map;
import java.util.Set;
import java.util.stream.Stream;
import java.util.stream.Collectors;

...

Map<Integer, Set<String>> collected = Stream.of(
        Map.entry(123, "foo"),
        Map.entry(123, "bar"),
        Map.entry(234, "baz"))
        .collect(Collectors.groupingBy(
                Map.Entry::getKey,
                Collectors.mapping(
                        Map.Entry::getValue, Collectors.toSet()
                )
        ));

The classifier in the above groupingBy is simply the key of the source map: Map.Entry::getKey. This determines which group each element will belong to.

Then there is a downstream mapping: Collectors.mapping(...). This controls how the elements are transformed before being saved: each input value is placed in a set.

The result:

{234=[baz], 123=[bar, foo]}

The above example used groupingBy(). There is also groupingByConcurrent() which performs the same grouping but collects its data into a ConcurrentHashMap instead of HashMap:

Not at all relevant in my small example above - but could be, for larger data sets where concurrent/parallel execution across multiple threads may make a performance difference.

Group by Two Fields

Nested groupings populate the nested map:

1
2
3
4
final Map<Employee.Gender, Map<Integer, List<Employee>>> groupByGenderAndAge = employees.stream()
                .collect(groupingBy(Employee::getGender,
                         groupingBy(Employee::getAge))
                );

Sort a List

1
2
3
4
5
6
public static <T> List<T> sortList(List<T> unsortedList) {
    return unsortedList
            .stream()
            .sorted()
            .collect(Collectors.toList());
}

Count Items in a List

This includes distinct(), so it provides a count of unique values:

1
items.stream().distinct().count();

Get List of Fields from a List

We start with a list of objects - in this case courses, where each course has a name and a class room number:

1
2
3
4
List<Course> courses = new ArrayList<>();
courses.add(new Course("Math", "123"));
courses.add(new Course("Physics", "456"));
courses.add(new Course("Chemistry", "789"));

Then we build a new list containing just the room numbers. In this example, we then count the number of distinct room numbers:

1
2
3
4
5
6
7
8
import static java.util.stream.Collectors.toList;

...

long roomsCount = courses.stream()
        .map(Course::getClassRoom)    // get the room name from each course
        .collect(toList())            // build a list of these room names
        .stream().distinct().count(); // count the number of unique room names

Stringify a List

Create a comma-separated list of items in a String, from data in a List:

1
2
String sorted = myList.stream().map(item -> item.getItemName())
            .collect(Collectors.joining(", "));

Check Items in a Collection

Three examples of the same basic thing:

Traditional for loop

The traditional way using a for() loop:

1
2
3
4
5
6
7
8
boolean nameMatch = Boolean.FALSE;
for (ContentType ct : contentTypes) {
    if (ct.getContentTypeName().equals("movie")) {
        nameMatch = Boolean.TRUE;
        break;
    }
}
assertThat(nameMatch).isTrue();

Stream forEachOrdered

Using a filter .forEach():

1
2
3
4
5
6
contentTypes
        .stream()
        .filter(ct -> ct.equals(expectedType))
        .forEachOrdered((ct) -> {
            assertThat(ct.getContentTypeName()).isEqualTo("movie");
        });

Stream ifPresentOrElse

Using a filter .ifPresentOrElse()

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
contentTypes
        .stream()
        .filter(ct -> ct.equals(expectedType))
        .findFirst()
        .ifPresentOrElse(
            value -> {
                assertThat(value.getContentTypeName())
                    .isEqualTo("movie");
            },
            () -> {
                assertThat(false);
            });

At this point I am missing the simplicity of the traditional syntax.

Map of Lists to Set

Convert a Map<String, List<String>> to Set<String>.

Traditional forEach()

1
map.values().forEach(l -> l.forEach(stringValues::add));

Stream flatMap()

1
2
3
4
Set<String> stringValues = map.values()
   .stream()
   .flatMap(List::stream)
   .collect(Collectors.toCollection(HashSet::new));

Here, flatMap() flattens the nested list inside the map.

Process Map of Maps

Convert text in the following structure to upper case:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
Map<String,Map<String,String>> input = Map
        .of("abc",
            Map.of("Def", "Ghi"),
            "jkl",
            Map.of("MNO", "PQR", "stu", "vwx")
        );

Map<String,Map<String,String>> output = input.entrySet().stream()
        .collect(Collectors.toMap(
                e1 -> e1.getKey().toUpperCase(),
                e1 -> e1.getValue().entrySet().stream().collect(Collectors.toMap(
                        e2 -> e2.getKey().toUpperCase(),
                        e2 -> e2.getValue().toUpperCase()))));

System.out.println(input);
System.out.println(output);

Results:

{abc={Def=Ghi}, jkl={stu=vwx, MNO=PQR}}

{ABC={DEF=GHI}, JKL={STU=VWX, MNO=PQR}}

Note: The code will fail with IllegalStateException: Duplicate key if there are 2 keys that will become the same when uppercased.

Custom Sorting

Start with this:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
List<Map<String,String>> list = new ArrayList<>();
Map<String,String> map = new HashMap<>();
Map<String,String> map1 = new HashMap<>();
Map<String,String> map2 = new HashMap<>();
map.put("productNumber", "107-001");
map1.put("productNumber", "108-001");
map2.put("productNumber", "109-001");
map.put("price", "1.99");
map1.put("price", "1.02");
map2.put("price", "1.99");
list.add(map);
list.add(map1);
list.add(map2);

Objective: Sort by key1 in descending order, key2 ascending order.

priceproductNumber
1.99107-001
1.99109-001
1.02108-001

The following uses comparator chaining:

1
2
3
4
Comparator<Map<String, String>> comp = Comparator
    .comparing(m ->Double.parseDouble(m.get("price"))
         ,Comparator.reverseOrder());
comp = comp.thenComparing(m -> m.get("productNumber"));

Apply it as follows:

1
2
3
4
5
6
List<Map<String, String>> formattedResult =
                list.stream().sorted(comp)  
                        .collect(Collectors.toList())

        formattedResult.forEach(m -> System.out.println(
                m.get("price") + " : " + m.get("productNumber")));

Compute If Absent

Assume we have a set of client IDs. Each ID can have many invoices, each invoice generated at a specific datetime.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
private void doComputeIfAbsent() {
    int id = 0;
    Invoice invoice = new Invoice();
    LocalDateTime date = LocalDateTime.now();

    HashMap<Integer, HashMap<LocalDateTime, Invoice>> allInvoicesAllClients = new HashMap();
    HashMap<LocalDateTime, Invoice> allInvoices = allInvoicesAllClients.get(id);

    // sub-optimal:
    if (allInvoices != null) {
        allInvoices.put(date, invoice);      //<---REPEATED CODE                  
    } else {
        allInvoices = new HashMap<>();
        allInvoices.put(date, invoice);      //<---REPEATED CODE
        allInvoicesAllClients.put(id, allInvoices);
    }

    // If id isn't present as a key in allInvoicesAllClients, then
    // it'll create mapping from id to a new HashMap and return the
    // new HashMap. If id is present as a key, then it'll return the
    // existing HashMap. See:
    // https://docs.oracle.com/en/java/javase/13/docs/api/java.base/java/util/Map.html#computeIfAbsent(K,java.util.function.Function)
    allInvoicesAllClients.computeIfAbsent( id, k -> new HashMap() ).put(date, invoice);

}

Get Last n Entries

Sort a linked hash map in reverse order by value, in order to get the last (i.e. now the first) n entries:

1
Map<String, Double> indDistance = new LinkedHashMap<String, Double>();

The comparator:

1
2
3
4
5
public class ReverseDoubleComparator implements java.util.Comparator<Double> {
    public int compare(Double d1, Double d2) {
        return (d1.compareTo(d2)) * (-1);
    }
}

Now use this Comparator in sorted() method, i.e.:

1
2
3
4
Map sortedInds =
        indDistance.entrySet().stream()
        .sorted(Entry.comparingByValue(new ReverseDoubleComparator()))
        .collect(Collectors.toMap(Entry::getKey, Entry::getValue, (e1, e2) -> e1, LinkedHashMap::new));

To Stream or Not to Stream

When not to use a Stream - when it’s simpler:

1
yourMap.entrySet().removeIf(k -> someRemovalCheck());

…compared to this:

1
2
3
4
Map<Integer, String> map = new HashMap<>(Map.of(1, "One", 2, "Two", 3, "Three"));
System.out.println(map);
map.values().removeIf("Three"::equals);
System.out.println(map);