Chapter 10. Java 9 Additions

At the time of this writing, Java SDK 9 is considered feature complete, but not yet released. The new feature garnering the most press coverage is Project Jigsaw, which introduces a new modularization mechanism into the language.

This chapter contains recipes involving the new additions, like private methods in interfaces, factory methods for immutable collections, and the new methods for streams, Optional, and Collectors. Each of the recipes has been tested with Java SE 9 Early Access build 174.

FYI, the new features in Java 9 not covered in this chapter are:

  • The jshell interactive console

  • The modified try-with-resources block

  • The relaxed syntax for the diamond operator

  • The new deprecation warnings

  • The reactive streams classes

  • The stack-walking API

  • The revised Process class

Several are relatively minor (like the diamond operator changes, try-with-resources requirements, and deprecation warnings). Some are specialty topics (like the stack-walking API and the changes to the Process API). The new shell is covered heavily in the documentation, along with a tutorial.

Finally, the reactive streams additions are fascinating, but the open source community already provides APIs like Reactive Streams, RxJava, and more, and it might be a good idea to wait to see how the community decides to support the new Java 9 API.

The recipes in this chapter hopefully cover the most common use cases. Should that turn out not to be the case, more recipes will be added in the next edition of this book.1

The recipes in this chapter also have a different feel to them from the rest of the book. This book has been use-case driven, in that each recipe is supposed to solve a particular type of problem. In this chapter, some of the recipes are just placeholders for discussions about new features in the API.

10.1 Modules in Jigsaw

Problem

You want to access Java modules from the standard library, and encapsulate your own code in modules.

Solution

Learn the basics of Jigsaw modules and learn how to use the modularized JDK. Then wait for the final release of Java 9 to make any upgrade-related decisions.

Discussion

JSR 376, the Java Platform Module System, is both the biggest change coming in Java 9 and the most controversial one. Attempts have been made to modularize Java for nearly ten years,2 with varying degrees of success and adoption, culminating in JPMS.

While the goal of “strong” encapsulation enforced by a module system has benefits for maintenance, no new feature comes without a cost. In this case, making such a fundamental change to a language with twenty years of backward compatibility to maintain is bound to be difficult.

For example, the concept of modules changes the nature of public and private. If a module does not export a particular package, you can’t access classes inside it even if they are made public. Likewise, you can no longer use reflection to access non-public members of a class that isn’t in an exported package. This affects reflection-based libraries and frameworks (including popular ones like Spring and Hibernate), as well as virtually every non-Java language on the JVM. As a concession, the team has proposed that a command-line flag called --illegal-access=permit will be the default in Java 9 and disallowed in a future release.

At the time of this writing (late June 2017), the inclusion of the JPMS specification in Java 9 has been rejected once, but is under revision in preparation for another vote.3 In addition, the release date of Java 9 has been pushed back to late September 2017.

Still, it is likely that some form of Jigsaw will be included in Java 9, and its basic capabilities are well established. The purpose of this recipe is to give you the necessary background on those basics, so that if and when the JPMS system is adopted, you’ll be ready to take advantage of it.

The first thing to know is that you do not have to modularize your own code. The Java libraries have been modularized, and other dependency libraries are the process of doing so, but you can wait to do the same for your own code until the system stabilizes.

Modules

The new system defines modules, which have a name (except for the so-called unnamed module) and express their dependencies and export packages via a file called module-info.java. A module includes a compiled module-info.class inside its deliverable JAR. The module-info.java file is known as a module descriptor.

The contents of module-info.java start with the word module and then use a combination of requires and exports keywords to describe what the module does. To demonstrate this, following is a trivial “Hello, World!” example that will use two modules and the JVM.

The example modules are com.oreilly.suppliers and com.kousenit.clients.

Tip

The “reversed URL” pattern is currently the recommended naming convention for modules.

The former supplies a Stream of strings representing names. The latter prints each name to the console with a welcome message.

For the Supplier module, the source code for the NamesSupplier class is shown in Example 10-1.

Example 10-1. Supplying a stream of names
package com.oreilly.suppliers;

// imports ...

public class NamesSupplier implements Supplier<Stream<String>> {
    private Path namesPath = Paths.get("server/src/main/resources/names.txt");

    @Override
    public Stream<String> get() {
        try {
            return Files.lines(namesPath);
        } catch (IOException e) {
            e.printStackTrace();
            return null;
        }
    }
}

(The module is stored in an IntelliJ module—unfortunately IntelliJ IDEA also uses the word “module” for a different concept—called “server,” which is why that name is in the path for the text file.)

The contents of names.txt are:4

Londo
Vir
G'Kar
Na'Toth
Delenn
Lennier
Kosh

In the client module, the source code for the Main class is in Example 10-2.

Example 10-2. Printing the names
package com.kousenit.clients;

// imports ...

public class Main {
    public static void main(String[] args) throws IOException {
        NamesSupplier supplier = new NamesSupplier();

        try (Stream<String> lines = supplier.get()) {  1
            lines.forEach(line -> System.out.printf("Hello, %s!%n", line));
        }
    }
}
1

try-with-resources auto-closes stream

The module-info.java file for the Supplier code is shown in Example 10-3.

Example 10-3. Defining the Supplier module
module com.oreilly.suppliers {      1
    exports com.oreilly.suppliers;  2
}
1

Module name

2

Make module available to others

The module-info.java file for the client module is shown in Example 10-4.

Example 10-4. Defining the client module
module com.kousenit.clients {       1
    requires com.oreilly.suppliers; 2
}
1

Module name

2

Needs Supplier module

When this program is executed, the output is:

Hello, Vir!
Hello, G'Kar!
Hello, Na'Toth!
Hello, Delenn!
Hello, Lennier!
Hello, Kosh!

The exports clause is necessary in the Supplier module for the NamesSupplier class to be visible to the client. The requires clause in the client module tells the system that this module needs classes from the Supplier module.

If you would like to log accesses to the server in that module, you can add a Logger from the java.util.logging package in the JVM, as in Example 10-5.

Example 10-5. Add logging to Supplier module
public class NamesSupplier implements Supplier<Stream<String>> {
    private Path namesPath = Paths.get("server/src/main/resources/names.txt");
    private Logger logger = Logger.getLogger(this.getClass().getName()); 1

    @Override
    public Stream<String> get() {
        logger.info("Request for names on " + Instant.now());            2
        try {
            return Files.lines(namesPath);
        } catch (IOException e) {
            e.printStackTrace();
            return null;
        }
    }
}
1

Create a Java util logger

2

Log accesses with a timestamp

This code will not compile. The JVM has been modularized as part of Java 9, and the java.util.logging package is not part of java.base, which is the only module provided by the JVM by default. In order to use the Logger class, you need to update the module-info.java file to match that in Example 10-6.

Example 10-6. Updated module-info.java file
module com.oreilly.suppliers {
    requires java.logging;          1
    exports com.oreilly.suppliers;
}
1

Require a module from the JVM other than java.base

The JVM modules are each documented with their own module-info.java files. For instance, Example 10-7 shows the module-info.java file from the java.logging module.

Example 10-7. The module-info.java file for the Java Logging API
module java.logging {
    exports java.util.logging;
    provides jdk.internal.logger.DefaultLoggerFinder with
        sun.util.logging.internal.LoggingProviderImpl;
}

This file does not only exports the module. It also provides an internal implementation of a Service Provider Interface (SPI) DefaultLoggerFinder in the form of the Logging​ProviderImpl class when a logger is requested by a client.

Note

Jigsaw also establishes mechanisms for working with service locators and providers. See the documentation for details.

Hopefully this gives you a sense of how modules are defined and how they work together. Expect to hear much more about this in the coming months.

There are many more issues related to modules that will be resolved before the specification is approved. Many of them involve porting legacy code. Terms like the unnamed module and automatic modules involve code that is not in any module but on the “module path,” and modules formed by existing legacy JAR files. Much of the debate about JPMS is about how to handle those cases.

See Also

The development of Jigsaw is part of the Open JDK project. See the quick-start guide at http://openjdk.java.net/projects/jigsaw/quick-start. The current documentation is at http://openjdk.java.net/projects/jigsaw/spec/sotms/ (entitled “State of the Module System”).

10.2 Private Methods in Interfaces

Problem

You want to add private methods to interfaces that can be called by other methods in the interface.

Solution

Java SE 9 now supports using the private keyword on interface methods.

Discussion

In Java SE 8, for the first time developers could add implementations to interface methods, labeling them as default or static. The next logical step was to add pri⁠vate methods as well.

Private methods use the keyword private and must have an implementation. Like private methods in classes, they cannot be overridden. Even more, they can only be invoked from within the same source file.

Example 10-8 is somewhat contrived, but still illustrative.

Example 10-8. Private method in an interface
import java.util.function.IntPredicate;
import java.util.stream.IntStream;

public interface SumNumbers {
    default int addEvens(int... nums) {
        return add(n -> n % 2 == 0, nums);
    }

    default int addOdds(int... nums) {
        return add(n -> n % 2 != 0, nums);
    }

    private int add(IntPredicate predicate, int... nums) { 1
        return IntStream.of(nums)
                .filter(predicate)
                .sum();
    }
}
1

Private method

The addEvens and addOdds methods are both public (because the default access in an interface is public) and take a variable argument list of integers as an argument. The provided default implementation for each delegates to the add method, which also takes an IntPredicate as an argument. By making add private, it is not accessible to any client, even through a class that implements the interface.

Example 10-9 shows how the method is used.

Example 10-9. Testing the private interface method
class PrivateDemo implements SumNumbers {} 1

import org.junit.Test;
import static org.junit.Assert.*;

public class SumNumbersTest {
    private SumNumbers demo = new PrivateDemo();

    @Test
    public void addEvens() throws Exception {
        assertEquals(2 + 4 + 6, demo.addEvens(1, 2, 3, 4, 5, 6)); 2
    }

    @Test
    public void addOdds() throws Exception {
        assertEquals(1 + 3 + 5, demo.addOdds(1, 2, 3, 4, 5, 6));  2
    }
}
1

Class that implements the interface

2

Invoking public methods that delegate to the private method

You can only instantiate a class, so an empty class called PrivateDemo is created that implements the SumNumbers interface. That class is instantiated, and its public interface methods can be invoked.

10.3 Creating Immutable Collections

Problem

You want to create immutable lists, sets, or maps in Java 9.

Solution

Use the static factory methods List.of, Set.of, and Map.of available in Java 9.

Discussion

The Javadocs on Java 9 state that the List.of() static factory methods provide a convenient way to create immutable lists. The List instances created by these methods have the following characteristics:

  • They are structurally immutable. Elements cannot be added, removed, or replaced. Calling any mutator method will always cause UnsupportedOperation​Exception to be thrown. However, if the contained elements are themselves mutable, this may cause the List’s contents to appear to change.

  • They disallow null elements. Attempts to create them with null elements result in NullPointerException.

  • They are serializable if all elements are serializable.

  • The order of elements in the list is the same as the order of the provided arguments, or of the elements in the provided array.

  • They are serialized as specified on the Serialized Form page.

The available overloads of the of method for List are shown in Example 10-10.

Example 10-10. Static factory methods for creating immutable lists
static <E> List<E>	of()
static <E> List<E>	of(E e1)
static <E> List<E>	of(E e1, E e2)
static <E> List<E>	of(E e1, E e2, E e3)
static <E> List<E>	of(E e1, E e2, E e3, E e4)
static <E> List<E>	of(E e1, E e2, E e3, E e4, E e5)
static <E> List<E>	of(E e1, E e2, E e3, E e4, E e5, E e6)
static <E> List<E>	of(E e1, E e2, E e3, E e4, E e5, E e6, E e7)
static <E> List<E>	of(E e1, E e2, E e3, E e4, E e5, E e6, E e7, E e8)
static <E> List<E>	of(E e1, E e2, E e3, E e4, E e5, E e6, E e7, E e8, E e9)
static <E> List<E>	of(E e1, E e2, E e3, E e4, E e5, E e6, E e7, E e8, E e9,
    E e10)
static <E> List<E>	of(E... elements)

The resulting lists are, as the docs say, structurally immutable, so none of the normal mutator methods on List can be invoked: add, addAll, clear, remove, removeAll, replaceAll, and set all throw UnsupportedOperationException. A couple of test cases5 are shown in Example 10-11.

Example 10-11. Demonstrating immutability
@Test(expected = UnsupportedOperationException.class)
public void showImmutabilityAdd() throws Exception {
    List<Integer> intList = List.of(1, 2, 3);
    intList.add(99);
}

@Test(expected = UnsupportedOperationException.class)
public void showImmutabilityClear() throws Exception {
    List<Integer> intList = List.of(1, 2, 3);
    intList.clear();
}

@Test(expected = UnsupportedOperationException.class)
public void showImmutabilityRemove() throws Exception {
    List<Integer> intList = List.of(1, 2, 3);
    intList.remove(0);
}

@Test(expected = UnsupportedOperationException.class)
public void showImmutabilityReplace() throws Exception {
    List<Integer> intList = List.of(1, 2, 3);
    intList.replaceAll(n -> -n);
}

@Test(expected = UnsupportedOperationException.class)
public void showImmutabilitySet() throws Exception {
    List<Integer> intList = List.of(1, 2, 3);
    intList.set(0, 99);
}

If the contained objects are themselves mutable, however, a list of them can appear to change. Say you have a simple class that holds a mutable value, an in Example 10-12.

Example 10-12. A trivial class that holds a mutable value
public class Holder {
    private int x;

    public Holder(int x) { this.x = x; }

    public void setX(int x) {
        this.x = x;
    }

    public int getX() {
        return x;
    }
}

If you create an immutable list of holders, the values in the holders can change, which makes the list appear to change, as in Example 10-13.

Example 10-13. Changing the wrapped integer
@Test
public void areWeImmutableOrArentWe() throws Exception {
    List<Holder> holders = List.of(new Holder(1), new Holder(2)); 1
    assertEquals(1, holders.get(0).getX());

    holders.get(0).setX(4);                                       2
    assertEquals(4, holders.get(0).getX());
}
1

Immutable list of Holder instances

2

Change the contained value inside a Holder

This works, but it violates the spirit of the law, if not the letter. In other words, if you’re going to make an immutable list, try to have it contain immutable objects.

For sets (again from the Javadocs):

  • They reject duplicate elements at creation time. Duplicate elements passed to a static factory method result in IllegalArgumentException.

  • The iteration order of set elements is unspecified and is subject to change.

All of the of methods have the same signature as the corresponding List methods, except that they return Set<E>.

Maps are the same way, but the signatures of the of methods take alternating keys and values as arguments, as in Example 10-14.

Example 10-14. Static factory methods for creating immutable maps
static <K,V> Map<K,V>	of()
static <K,V> Map<K,V>	of(K k1, V v1)
static <K,V> Map<K,V>	of(K k1, V v1, K k2, V v2)
static <K,V> Map<K,V>	of(K k1, V v1, K k2, V v2, K k3, V v3)
static <K,V> Map<K,V>	of(K k1, V v1, K k2, V v2, K k3, V v3,
    K k4, V v4)
static <K,V> Map<K,V>	of(K k1, V v1, K k2, V v2, K k3, V v3,
    K k4, V v4, K k5, V v5)
static <K,V> Map<K,V>	of(K k1, V v1, K k2, V v2, K k3, V v3,
    K k4, V v4, K k5, V v5, K k6, V v6)
static <K,V> Map<K,V>	of(K k1, V v1, K k2, V v2, K k3, V v3,
    K k4, V v4, K k5, V v5, K k6, V v6, K k7, V v7)
static <K,V> Map<K,V>	of(K k1, V v1, K k2, V v2, K k3, V v3,
    K k4, V v4, K k5, V v5, K k6, V v6, K k7, V v7, K k8, V v8)
static <K,V> Map<K,V>	of(K k1, V v1, K k2, V v2, K k3, V v3,
    K k4, V v4, K k5, V v5, K k6, V v6, K k7, V v7, K k8, V v8,
    K k9, V v9)
static <K,V> Map<K,V>	of(K k1, V v1, K k2, V v2, K k3, V v3,
    K k4, V v4, K k5, V v5, K k6, V v6, K k7, V v7, K k8, V v8,
    K k9, V v9, K k10, V v10)
static <K,V> Map<K,V>	ofEntries(Map.Entry<? extends K,? extends V>... entries)

For creating maps of up to 10 entries, use the associated of methods that alternate the keys and the values. That can be awkward, so the interface also provides the of​Entries method and a static entry method for creating them:

static <K,V> Map<K,V> ofEntries(Map.Entry<? extends K,? extends V>... entries)
static <K,V> Map.Entry<K,V>	entry(K k, V v)

The code in Example 10-15 shows how to use those methods to create an immutable map.

Example 10-15. Immutable map from entries
@Test
public void immutableMapFromEntries() throws Exception {
    Map<String, String> jvmLanguages = Map.ofEntries(             1
        Map.entry("Java", "http://www.oracle.com/technetwork/java/index.html"),
        Map.entry("Groovy", "http://groovy-lang.org/"),
        Map.entry("Scala", "http://www.scala-lang.org/"),
        Map.entry("Clojure", "https://clojure.org/"),
        Map.entry("Kotlin", "http://kotlinlang.org/"));

    Set<String> names = Set.of("Java", "Scala", "Groovy", "Clojure", "Kotlin");
    List<String> urls = List.of("http://www.oracle.com/technetwork/java/index.html",
            "http://groovy-lang.org/",
            "http://www.scala-lang.org/",
            "https://clojure.org/",
            "http://kotlinlang.org/");

    Set<String> keys = jvmLanguages.keySet();
    Collection<String> values = jvmLanguages.values();

    names.forEach(name -> assertTrue(keys.contains(name)));
    urls.forEach(url -> assertTrue(values.contains(url)));

    Map<String, String> javaMap = Map.of("Java",                  2
            "http://www.oracle.com/technetwork/java/index.html",
            "Groovy",
            "http://groovy-lang.org/",
            "Scala",
            "http://www.scala-lang.org/",
            "Clojure",
            "https://clojure.org/",
            "Kotlin",
            "http://kotlinlang.org/");
    javaMap.forEach((name, url) -> assertTrue(
            jvmLanguages.keySet().contains(name) && \
              jvmLanguages.values().contains(url)));
}
1

Using Map.ofEntries

2

Using Map.of

The combination of the ofEntries and entry methods is a nice simplification.

See Also

Recipe 4.8 discusses how to create immutable collections using Java 8 or earlier.

10.4 Stream: ofNullable, iterate, takeWhile, and dropWhile

Problem

You want to use the new functionality added to streams in Java 9.

Solution

Use the new Stream methods ofNullable, iterate, takeWhile, and dropWhile.

Discussion

A few new methods have been added to the Stream interface in Java 9. This recipe will show how to use ofNullable, iterate, takeWhile, and dropWhile.

The ofNullable method

In Java 8, the Stream interface has an overloaded static factory method called of, which takes either a single value or a variable argument list. Either way, you can’t use a null argument.

In Java 9, the ofNullable method lets you create a single-element stream that wraps a value if not null, or is an empty stream otherwise. See the test case in Example 10-16 for details.

Example 10-16. Using Stream.ofNullable(arg)
@Test
public void ofNullable() throws Exception {
    Stream<String> stream = Stream.ofNullable("abc");  1
    assertEquals(1, stream.count());

    stream = Stream.ofNullable(null);                  2
    assertEquals(0, stream.count());
}
1

Stream with one element

2

Returns Stream.empty()

The count method returns the number of nonempty elements in a stream. You can now use the ofNullable method on any argument without checking whether or not it’s null first.

Using iterate with a Predicate

The next interesting method is a new overload for iterate. The iterate method in Java 8 has the signature:

static<T> Stream<T> iterate(final T seed, final UnaryOperator<T> f)

So creating a stream starts with a single element (the seed), and subsequent elements are produced by successively applying the unary operator. The result is an infinite stream, so using it normally requires a limit or some other short-circuiting function, like findFirst or findAny.

The new overloaded version of iterate takes a Predicate as the second argument:

static<T> Stream<T> iterate(T seed, Predicate<? super T> hasNext,
    UnaryOperator<T> next)

The values are produced by starting with the seed and then applying the unary operator as long as the values satisfy the hasNext predicate.

For instance, see Example 10-17.

Example 10-17. Iterate with a Predicate
@Test
public void iterate() throws Exception {
    List<BigDecimal> bigDecimals =                1
            Stream.iterate(BigDecimal.ZERO, bd -> bd.add(BigDecimal.ONE))
            .limit(10)
            .collect(Collectors.toList());

    assertEquals(10, bigDecimals.size());

    bigDecimals = Stream.iterate(BigDecimal.ZERO,  2
            bd -> bd.longValue() < 10L,
            bd -> bd.add(BigDecimal.ONE))
            .collect(Collectors.toList());

    assertEquals(10, bigDecimals.size());
}
1

Java 8 way to create a stream of big decimals

2

Java 9 way

The first stream is the Java 8 way of using iterate with a limit. The second one uses a Predicate as the second argument. The result looks more like a traditional for loop.

takeWhile and dropWhile

The new methods takeWhile and dropWhile allow you to get portions of a stream based on a predicate. According to the Javadocs, on an ordered stream, takeWhile returns “the longest prefix of elements taken from this stream that match the given predicate,” starting at the beginning of the stream.

The dropWhile method does the opposite—it returns the remaining elements of the stream after dropping the longest prefix of elements that satisfy the predicate.

The code in Example 10-18 shows how they work on an ordered stream.

Example 10-18. Taking and dropping from a stream
@Test
public void takeWhile() throws Exception {
    List<String> strings = Stream.of("this is a list of strings".split(" "))
            .takeWhile(s -> !s.equals("of"))  1
            .collect(Collectors.toList());
    List<String> correct = Arrays.asList("this", "is", "a", "list");
    assertEquals(correct, strings);
}

@Test
public void dropWhile() throws Exception {
    List<String> strings = Stream.of("this is a list of strings".split(" "))
            .dropWhile(s -> !s.equals("of"))  2
            .collect(Collectors.toList());
    List<String> correct = Arrays.asList("of", "strings");
    assertEquals(correct, strings);
}
1

Return stream up to where predicate fails

2

Return stream after predicate fails

Each method splits the stream at the same place, but takeWhile returns the elements before the split and dropWhile returns the elements after it.

The real advantage to takeWhile is that it is a short-circuiting operation. If you have a huge collection of sorted elements, you can stop evaluating once you hit the condition you care about.

For example, say you had a collection of orders from a client, sorted by value in descending order. Using takeWhile, you can get just the orders above a certain threshold, without having to apply a filter on every element.

The code in Example 10-19 simulates this situation by generating 50 random integers between 0 and 100, sorting them in descending order, and returning only those whose value is greater than 90.

Example 10-19. Taking ints above 90
Random random = new Random();
List<Integer> nums = random.ints(50, 0, 100) 1
        .boxed()                             2
        .sorted(Comparator.reverseOrder())
        .takeWhile(n -> n > 90)              3
        .collect(Collectors.toList());
1

Generate 50 random ints between 0 and 100

2

Box them so they can be sorted with a Comparator and collected

3

Split the stream and return the values greater than 90

This particular example is perhaps more intuitive (though not necessarily more efficient) using dropWhile instead, as in Example 10-20.

Example 10-20. Using dropWhile on the integer stream
Random random = new Random();
List<Integer> nums = random.ints(50, 0, 100)
        .sorted()                1
        .dropWhile(n -> n < 90)  2
        .boxed()
        .collect(Collectors.toList());
1

Sorted in ascending order

2

Split after the last value less than 90

Methods like takeWhile and dropWhile have existed in other languages for years. In Java 9 they’re available to Java as well.

10.5 Downstream Collectors: filtering and flatMapping

Problem

You want to filter elements as part of a downstream collector, or flatten a generated collection of collections.

Solution

Java 9 introduces the filtering and flatMapping methods in Collectors for those purposes.

Discussion

Java 8 introduced a groupingBy operation in Collectors, so that you can group objects by a particular property. Grouping operations produce a map of keys to lists of values. Java 8 also allows you to use downstream collectors, so that instead of generating lists, you can postprocess the lists to get their sizes, or map them to something else, and so on.

Java 9 introduced two new downstream collectors: filtering and flatMapping.

The filtering method

Say you have a class called Task that has attributes for a budget and a list of developers working on it, which are represented by instances of a Developer class. Both classes are shown in Example 10-21.

Example 10-21. Tasks and Developers
public class Task {
    private String name;
    private long budget;
    private List<Developer> developers = new ArrayList<>();

    // ... constructors, getters and setters, etc. ...
}

public class Developer {
    private String name;

    // ... constructors, getters and setters, etc. ...
}

First, say you want to group the tasks by budget. A simple Collectors.groupingBy operation is shown in Example 10-22.

Example 10-22. Grouping tasks by budget
Developer venkat = new Developer("Venkat");
Developer daniel = new Developer("Daniel");
Developer brian = new Developer("Brian");
Developer matt = new Developer("Matt");
Developer nate = new Developer("Nate");
Developer craig = new Developer("Craig");
Developer ken = new Developer("Ken");

Task java = new Task("Java stuff", 100);
Task altJvm = new Task("Groovy/Kotlin/Scala/Clojure", 50);
Task javaScript = new Task("JavaScript (sorry)", 100);
Task spring = new Task("Spring", 50);
Task jpa = new Task("JPA/Hibernate", 20);

java.addDevelopers(venkat, daniel, brian, ken);
javaScript.addDevelopers(venkat, nate);
spring.addDevelopers(craig, matt, nate, ken);
altJvm.addDevelopers(venkat, daniel, ken);

List<Task> tasks = Arrays.asList(java, altJvm, javaScript, spring, jpa);

Map<Long, List<Task>> taskMap = tasks.stream()
        .collect(groupingBy(Task::getBudget));

This results in a Map of budget amounts to lists of tasks with that budget:

 50: [Groovy/Kotlin/Scala/Clojure, Spring]
 20: [JPA/Hibernate]
100: [Java stuff, JavaScript (sorry)]

Now, if you only want tasks that have a budget that exceeds some threshold, you can add a filter operation, as in Example 10-23.

Example 10-23. Grouping with a filter
taskMap = tasks.stream()
        .filter(task -> task.getBudget() >= THRESHOLD)
        .collect(groupingBy(Task::getBudget));

The output for a threshold of 50 is:

 50: [Groovy/Kotlin/Scala/Clojure, Spring]
100: [Java stuff, JavaScript (sorry)]

Tasks with budgets below the threshold will not appear in the output map at all. If you want to see them anyway, you now have an alternative. In Java 9, the Collectors class now has an additional static method called filtering, similar to filter, but applied to the downstream list of tasks. The code in Example 10-24 shows how to use it.

Example 10-24. Grouping with a downstream filter
taskMap = tasks.stream()
    .collect(groupingBy(Task::getBudget,
          filtering(task -> task.getBudget() >= 50, toList())));

Now all the budget values will appear as keys, but the tasks whose budgets fall below the threshold will not appear in the list values:

 50: [Groovy/Kotlin/Scala/Clojure, Spring]
 20: []
100: [Java stuff, JavaScript (sorry)]

The filtering operation is thus a downstream collector, operating on the list generated by the grouping operation.

The flatMapping method

This time, say you want to get a list of developers on each task. The basic grouping operation produces a group of task names to lists of tasks, as in Example 10-25.

Example 10-25. Grouping tasks by names
Map<String, List<Task>> tasksByName = tasks.stream()
        .collect(groupingBy(Task::getName));

The (formatted) output is:

                 Java stuff: [Java stuff]
Groovy/Kotlin/Scala/Clojure: [Groovy/Kotlin/Scala/Clojure]
         JavaScript (sorry): [JavaScript (sorry)]
                     Spring: [Spring]
               JPA/Hibenate: [JPA/Hibernate]

To get the associated lists of developers, you can use the mappingBy downstream collector, as in Example 10-26.

Example 10-26. Lists of developers for each task
Map<String, Set<List<Developer>>> map = tasks.stream()
        .collect(groupingBy(Task::getName,
              Collectors.mapping(Task::getDevelopers, toSet())));

As the return type shows, the problem is that it returns a Set<List<Developer>>. What’s needed here is a downstream flatMap operation to flatten the collection of collections. That’s now possible using the flatMapping method on Collectors, as in Example 10-27.

Example 10-27. Using flatMapping to get just a set of developers
Map<String, Set<Developer>> task2setdevs = tasks.stream()
        .collect(groupingBy(Task::getName,
                Collectors.flatMapping(task -> task.getDevelopers().stream(),
                toSet())));

Now the result is what you want:

                 Java stuff: [Daniel, Brian, Ken, Venkat]
Groovy/Kotlin/Scala/Clojure: [Daniel, Ken, Venkat]
         JavaScript (sorry): [Nate, Venkat]
                     Spring: [Craig, Ken, Matt, Nate]
              JPA/Hibernate: []

The flatMapping method is just like the flatMap method on Stream. Note that the first argument flatMapping needs to be a stream, which can be empty or not depending on the source.

See Also

Downstream collectors are discussed in Recipe 4.6. The flatMap operation is in Recipe 3.11.

10.6 Optional: stream, or, ifPresentOrElse

Problem

You want to flat map Optionals into a stream of contained elements, or you want to choose from several possibilities, or you want to do something if an element is present and return a default otherwise.

Solution

Use the new stream, or, or ifPresentOrElse methods in Optional.

Discussion

The Optional class, introduced in Java 8, provides a way to indicate to a client that a returned value may legitimately be null. Rather than returning null, you return an empty Optional. That makes Optional a good wrapper for methods that may or may not return a value.

The stream method

Consider a finder method to look up customers by ID, as in Example 10-28.

Example 10-28. Find a customer by ID
public Optional<Customer> findById(int id) {
    return Optional.ofNullable(map.get(id));
}

This method assumes that the customers are contained within a Map in memory. The get method on Map returns a value if the key is present or null if not, so making it the argument to Optional.ofNullable either wraps a nonnull value inside an Optional or returns an empty Optional.

Tip

Remember that since the Optional.of method throws an exception if its argument is null, the Optional.ofNullable(arg) is a convenient shortcut. Its implementation is arg != null ? Optional.of(arg) : Optional.empty().

Since findById returns an Optional<Customer>, trying to return a collection of customers is a bit more complicated. In Java 8, you can write the code in Example 10-29.

Example 10-29. Using a filter and a Map on Optionals
public Collection<Customer> findAllById(Integer... ids) {
    return Arrays.stream(ids)
            .map(this::findById)           1
            .filter(Optional::isPresent)   2
            .map(Optional::get)            3
            .collect(Collectors.toList());
}
1

Maps to a Stream<Optional<Customer>>

2

Filter out any empty Optionals

3

Call get, so maps to a Stream<Customer>

This isn’t too much of a hardship, but Java 9 has made the process simpler by adding the stream method to Optional, whose signature is:

Stream<T> stream()

If a value is present, this method returns a sequential, single element stream containing only that value. Otherwise it returns an empty stream. This method means that a stream of optional customers can be turned into a stream of customers directly, as in Example 10-30.

Example 10-30. Using flatMap with Optional.stream
public Collection<Customer> findAllById(Integer... ids) {
    return Arrays.stream(ids)
            .map(this::findById)            1
            .flatMap(Optional::stream)      2
            .collect(Collectors.toList());
}
1

Map to Stream<Optional<Customer>>

2

Flat map to Stream<Customer>

This is purely a convenience method, but a useful one.

The or method

The orElse method is used to extract a value from an Optional. It takes a default as an argument:

Customer customer = findById(id).orElse(Customer.DEFAULT)

There is also the orElseGet method that uses a Supplier to create the default, in the case where doing so is an expensive operation:

Customer customer = findById(id).orElseGet(() -> createDefaultCustomer())

Both of those return Customer instances. The or method on Optional, added in Java 9, allows you to return an Optional<Customer> instead, given a Supplier of them, so you can chain alternative ways of finding customers together.

The signature of the or method is:

Optional<T> or(Supplier<? extends Optional<? extends T>> supplier)

If a value is present, this method returns an Optional describing it. Otherwise it invokes the Supplier and returns the Optional it returns.

Therefore, if we have multiple ways to find a customer, you can now write the code in Example 10-31.

Example 10-31. Using the or method to try alternatives
public Optional<Customer> findById(int id) {
    return findByIdLocal(id)
            .or(() -> findByIdRemote(id))
            .or(() -> Optional.of(Customer.DEFAULT));

This method searches for the customer in the local cache and then accesses some remote server. If neither of those finds a nonempty Optional, the final clause creates a default, wraps it in an Optional, and returns it instead.

The ifPresentOrElse method

The ifPresent method on Optional executes a Consumer if the Optional is not empty, as in Example 10-32.

Example 10-32. Using ifPresent to only print nonempty customers
public void printCustomer(Integer id) {
    findByIdLocal(id).ifPresent(System.out::println);  1
}

public void printCustomers(Integer... ids) {
    Arrays.asList(ids)
            .forEach(this::printCustomer);
}
1

Only prints for nonempty Optionals

This works, but you might want to run something else if the returned Optional is empty. The new ifPresentOrElse method takes a second, Runnable, argument that is executed if the Optional is empty. Its signature is:

void ifPresentOrElse(Consumer<? super T> action, Runnable emptyAction)

To use it, simply provide a lambda that takes no arguments and returns void, as in Example 10-33.

Example 10-33. Printing a customer or a default message
public void printCustomer(Integer id) {
    findByIdLocal(id).ifPresentOrElse(System.out::println,
            () -> System.out.println("Customer with id=" + id + " not found"));
}

This version prints the customer if one is found, and prints a default message otherwise.

None of these additions to Optional change its behavior in any fundamental way, but they do provide conveniences when applicable.

See Also

The recipes in Chapter 6 cover the Optional class in Java 8.

10.7 Date Ranges

Problem

You want a stream of dates between two given endpoints.

Solution

Use the new datesUntil method in the LocalDate class, added in Java 9.

Discussion

The new Date-Time API added in Java 8 is an enormous improvement over classes like Date, Calendar, and TimeStamp in java.util, but one of the additions in Java 9 addresses an annoying hole in the API: there’s no easy way to create a stream of dates.

In Java 8, the easiest way to create a stream of dates is to start with an initial date and add an offset. For example, if you want all the days given endpoints a week apart, you can write the code in Example 10-34.

Example 10-34. Days between two dates (WARNING: BUG!)
public List<LocalDate> getDays_java8(LocalDate start, LocalDate end) {
    Period period = start.until(end);
    return IntStream.range(0, period.getDays())  1
            .mapToObj(start:plusDays)
            .collect(Collectors.toList());
}
1

Trap! See next example.

This works by determining the Period between the two dates and then creating an IntStream of days between them. Looking at days a week apart gives:

LocalDate start = LocalDate.of(2017, Month.JUNE, 10);
LocalDate end = LocalDate.of(2017, Month.JUNE, 17);
System.out.println(dateRange.getDays_java8(start, end));

// [2017-06-10, 2017-06-11, 2017-06-12, 2017-06-13,
//  2017-06-14, 2017-06-15, 2017-06-16]

This seems to work, but there’s actually a trap here. If you change the end date to exactly one month from the start date, the problem is obvious:

LocalDate start = LocalDate.of(2017, Month.JUNE, 10);
LocalDate end = LocalDate.of(2017, Month.JULY, 17);
System.out.println(dateRange.getDays_java8(start, end));

// []

No values are returned. The problem is that the getDays method on Period returns the days field from the period, not the number of days total. (The same is true about getMonths, getYears, and so on.) So if the days are the same, even though the months are different, the result is a range of size zero.

The proper way to handle this problem is to use the ChronoUnit enum, which implements the TemporalUnit interface and defines constants for DAYS, MONTHS, etc. The proper implementation for Java 8 is given in Example 10-35.

Example 10-35. Days between two dates (WORKS)
public List<LocalDate> getDays_java8(LocalDate start, LocalDate end) {
    Period period = start.until(end);
    return LongStream.range(0, ChronoUnit.DAYS.between(start, end)) 1
            .mapToObj(start:plusDays)
            .collect(Collectors.toList());
}
1

Works

You can also use the iterate method, but that requires you to know the number of days, as in Example 10-36.

Example 10-36. Iterating on LocalDate
public List<LocalDate> getDaysByIterate(LocalDate start, int days) {
    return Stream.iterate(start, date -> date.plusDays(1))
            .limit(days)
            .collect(Collectors.toList());
}

Fortunately, Java 9 makes all of this much simpler. Now the LocalDate class has a method called datesUntil, with an overload that takes a Period. The signatures are:

Stream<LocalDate> datesUntil(LocalDate endExclusive)
Stream<LocalDate> datesUntil(LocalDate endExclusive, Period step)

The version without a Period essentially calls the overload with a second argument of one day.

The Java 9 approach to the preceding problem is much simpler, as shown in Example 10-37.

Example 10-37. Date ranges in Java 9
public List<LocalDate> getDays_java9(LocalDate start, LocalDate end) {
    return start.datesUntil(end)                     1
            .collect(Collectors.toList());
}

public List<LocalDate> getMonths_java9(LocalDate start, LocalDate end) {
    return start.datesUntil(end, Period.ofMonths(1)) 2
            .collect(Collectors.toList());
}
1

Assumes Period.ofDays(1)

2

Counts in months

The datesUntil method produces a Stream, which can be manipulated with all the normal stream processing techniques.

See Also

Calculating the days between dates in Java 8 is part of Recipe 8.8.

1 They may even have the details of Jigsaw worked out by then. Here’s hoping. :)

2 The Jigsaw project itself was created in 2008.

3 In the second vote, which ended June 26, the JPMS specification was unanimously approved (with one abstention). See https://jcp.org/en/jsr/results?id=6016 for the detailed results.

4 It’s about time there was a Babylon 5 reference in this book. Presumably the space station was built out of modules, too (sorry).

5 The complete set of tests is in the source code for the book.