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.
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.
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.
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.
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.
packagecom.oreilly.suppliers;// imports ...publicclassNamesSupplierimplementsSupplier<Stream<String>>{privatePathnamesPath=Paths.get("server/src/main/resources/names.txt");@OverridepublicStream<String>get(){try{returnFiles.lines(namesPath);}catch(IOExceptione){e.printStackTrace();returnnull;}}}
(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.
packagecom.kousenit.clients;// imports ...publicclassMain{publicstaticvoidmain(String[]args)throwsIOException{NamesSuppliersupplier=newNamesSupplier();try(Stream<String>lines=supplier.get()){lines.forEach(line->System.out.printf("Hello, %s!%n",line));}}}
The module-info.java file for the Supplier code is shown in Example 10-3.
modulecom.oreilly.suppliers{exportscom.oreilly.suppliers;}
The module-info.java file for the client module is shown in Example 10-4.
modulecom.kousenit.clients{requirescom.oreilly.suppliers;}
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.
publicclassNamesSupplierimplementsSupplier<Stream<String>>{privatePathnamesPath=Paths.get("server/src/main/resources/names.txt");privateLoggerlogger=Logger.getLogger(this.getClass().getName());@OverridepublicStream<String>get(){logger.info("Request for names on "+Instant.now());try{returnFiles.lines(namesPath);}catch(IOExceptione){e.printStackTrace();returnnull;}}}
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.
modulecom.oreilly.suppliers{requiresjava.logging;exportscom.oreilly.suppliers;}
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.
modulejava.logging{exportsjava.util.logging;providesjdk.internal.logger.DefaultLoggerFinderwithsun.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 LoggingProviderImpl class when a logger is requested by a client.
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.
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”).
Java SE 9 now supports using the private keyword on interface methods.
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 private 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.
importjava.util.function.IntPredicate;importjava.util.stream.IntStream;publicinterfaceSumNumbers{defaultintaddEvens(int...nums){returnadd(n->n%2==0,nums);}defaultintaddOdds(int...nums){returnadd(n->n%2!=0,nums);}privateintadd(IntPredicatepredicate,int...nums){returnIntStream.of(nums).filter(predicate).sum();}}
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.
classPrivateDemoimplementsSumNumbers{}importorg.junit.Test;importstaticorg.junit.Assert.*;publicclassSumNumbersTest{privateSumNumbersdemo=newPrivateDemo();@TestpublicvoidaddEvens()throwsException{assertEquals(2+4+6,demo.addEvens(1,2,3,4,5,6));}@TestpublicvoidaddOdds()throwsException{assertEquals(1+3+5,demo.addOdds(1,2,3,4,5,6));}}
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.
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 UnsupportedOperationException 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.
static<E>List<E>of()static<E>List<E>of(Ee1)static<E>List<E>of(Ee1,Ee2)static<E>List<E>of(Ee1,Ee2,Ee3)static<E>List<E>of(Ee1,Ee2,Ee3,Ee4)static<E>List<E>of(Ee1,Ee2,Ee3,Ee4,Ee5)static<E>List<E>of(Ee1,Ee2,Ee3,Ee4,Ee5,Ee6)static<E>List<E>of(Ee1,Ee2,Ee3,Ee4,Ee5,Ee6,Ee7)static<E>List<E>of(Ee1,Ee2,Ee3,Ee4,Ee5,Ee6,Ee7,Ee8)static<E>List<E>of(Ee1,Ee2,Ee3,Ee4,Ee5,Ee6,Ee7,Ee8,Ee9)static<E>List<E>of(Ee1,Ee2,Ee3,Ee4,Ee5,Ee6,Ee7,Ee8,Ee9,Ee10)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.
@Test(expected=UnsupportedOperationException.class)publicvoidshowImmutabilityAdd()throwsException{List<Integer>intList=List.of(1,2,3);intList.add(99);}@Test(expected=UnsupportedOperationException.class)publicvoidshowImmutabilityClear()throwsException{List<Integer>intList=List.of(1,2,3);intList.clear();}@Test(expected=UnsupportedOperationException.class)publicvoidshowImmutabilityRemove()throwsException{List<Integer>intList=List.of(1,2,3);intList.remove(0);}@Test(expected=UnsupportedOperationException.class)publicvoidshowImmutabilityReplace()throwsException{List<Integer>intList=List.of(1,2,3);intList.replaceAll(n->-n);}@Test(expected=UnsupportedOperationException.class)publicvoidshowImmutabilitySet()throwsException{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.
publicclassHolder{privateintx;publicHolder(intx){this.x=x;}publicvoidsetX(intx){this.x=x;}publicintgetX(){returnx;}}
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.
@TestpublicvoidareWeImmutableOrArentWe()throwsException{List<Holder>holders=List.of(newHolder(1),newHolder(2));assertEquals(1,holders.get(0).getX());holders.get(0).setX(4);assertEquals(4,holders.get(0).getX());}
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.
static<K,V>Map<K,V>of()static<K,V>Map<K,V>of(Kk1,Vv1)static<K,V>Map<K,V>of(Kk1,Vv1,Kk2,Vv2)static<K,V>Map<K,V>of(Kk1,Vv1,Kk2,Vv2,Kk3,Vv3)static<K,V>Map<K,V>of(Kk1,Vv1,Kk2,Vv2,Kk3,Vv3,Kk4,Vv4)static<K,V>Map<K,V>of(Kk1,Vv1,Kk2,Vv2,Kk3,Vv3,Kk4,Vv4,Kk5,Vv5)static<K,V>Map<K,V>of(Kk1,Vv1,Kk2,Vv2,Kk3,Vv3,Kk4,Vv4,Kk5,Vv5,Kk6,Vv6)static<K,V>Map<K,V>of(Kk1,Vv1,Kk2,Vv2,Kk3,Vv3,Kk4,Vv4,Kk5,Vv5,Kk6,Vv6,Kk7,Vv7)static<K,V>Map<K,V>of(Kk1,Vv1,Kk2,Vv2,Kk3,Vv3,Kk4,Vv4,Kk5,Vv5,Kk6,Vv6,Kk7,Vv7,Kk8,Vv8)static<K,V>Map<K,V>of(Kk1,Vv1,Kk2,Vv2,Kk3,Vv3,Kk4,Vv4,Kk5,Vv5,Kk6,Vv6,Kk7,Vv7,Kk8,Vv8,Kk9,Vv9)static<K,V>Map<K,V>of(Kk1,Vv1,Kk2,Vv2,Kk3,Vv3,Kk4,Vv4,Kk5,Vv5,Kk6,Vv6,Kk7,Vv7,Kk8,Vv8,Kk9,Vv9,Kk10,Vv10)static<K,V>Map<K,V>ofEntries(Map.Entry<?extendsK,?extendsV>...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 ofEntries method and a static entry method for creating them:
static<K,V>Map<K,V>ofEntries(Map.Entry<?extendsK,?extendsV>...entries)static<K,V>Map.Entry<K,V>entry(Kk,Vv)
The code in Example 10-15 shows how to use those methods to create an immutable map.
@TestpublicvoidimmutableMapFromEntries()throwsException{Map<String,String>jvmLanguages=Map.ofEntries(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","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)));}
The combination of the ofEntries and entry methods is a nice simplification.
Recipe 4.8 discusses how to create immutable collections using Java 8 or earlier.
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.
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.
@TestpublicvoidofNullable()throwsException{Stream<String>stream=Stream.ofNullable("abc");assertEquals(1,stream.count());stream=Stream.ofNullable(null);assertEquals(0,stream.count());}
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.
The next interesting method is a new overload for iterate. The iterate method in Java 8 has the signature:
static<T>Stream<T>iterate(finalTseed,finalUnaryOperator<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(Tseed,Predicate<?superT>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.
@Testpublicvoiditerate()throwsException{List<BigDecimal>bigDecimals=Stream.iterate(BigDecimal.ZERO,bd->bd.add(BigDecimal.ONE)).limit(10).collect(Collectors.toList());assertEquals(10,bigDecimals.size());bigDecimals=Stream.iterate(BigDecimal.ZERO,bd->bd.longValue()<10L,bd->bd.add(BigDecimal.ONE)).collect(Collectors.toList());assertEquals(10,bigDecimals.size());}
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.
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.
@TestpublicvoidtakeWhile()throwsException{List<String>strings=Stream.of("this is a list of strings".split(" ")).takeWhile(s->!s.equals("of")).collect(Collectors.toList());List<String>correct=Arrays.asList("this","is","a","list");assertEquals(correct,strings);}@TestpublicvoiddropWhile()throwsException{List<String>strings=Stream.of("this is a list of strings".split(" ")).dropWhile(s->!s.equals("of")).collect(Collectors.toList());List<String>correct=Arrays.asList("of","strings");assertEquals(correct,strings);}
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.
Randomrandom=newRandom();List<Integer>nums=random.ints(50,0,100).boxed().sorted(Comparator.reverseOrder()).takeWhile(n->n>90).collect(Collectors.toList());
This particular example is perhaps more intuitive (though not necessarily more efficient) using dropWhile instead, as in Example 10-20.
Randomrandom=newRandom();List<Integer>nums=random.ints(50,0,100).sorted().dropWhile(n->n<90).boxed().collect(Collectors.toList());
Methods like takeWhile and dropWhile have existed in other languages for years. In Java 9 they’re available to Java as well.
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.
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.
publicclassTask{privateStringname;privatelongbudget;privateList<Developer>developers=newArrayList<>();// ... constructors, getters and setters, etc. ...}publicclassDeveloper{privateStringname;// ... 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.
Developervenkat=newDeveloper("Venkat");Developerdaniel=newDeveloper("Daniel");Developerbrian=newDeveloper("Brian");Developermatt=newDeveloper("Matt");Developernate=newDeveloper("Nate");Developercraig=newDeveloper("Craig");Developerken=newDeveloper("Ken");Taskjava=newTask("Java stuff",100);TaskaltJvm=newTask("Groovy/Kotlin/Scala/Clojure",50);TaskjavaScript=newTask("JavaScript (sorry)",100);Taskspring=newTask("Spring",50);Taskjpa=newTask("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.
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.
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.
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.
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.
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.
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.
Downstream collectors are discussed in Recipe 4.6. The flatMap operation is in Recipe 3.11.
Use the new stream, or, or ifPresentOrElse methods in Optional.
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.
Consider a finder method to look up customers by ID, as in Example 10-28.
publicOptional<Customer>findById(intid){returnOptional.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.
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.
publicCollection<Customer>findAllById(Integer...ids){returnArrays.stream(ids).map(this::findById).filter(Optional::isPresent).map(Optional::get).collect(Collectors.toList());}
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.
publicCollection<Customer>findAllById(Integer...ids){returnArrays.stream(ids).map(this::findById).flatMap(Optional::stream).collect(Collectors.toList());}
This is purely a convenience method, but a useful one.
The orElse method is used to extract a value from an Optional. It takes a default as an argument:
Customercustomer=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:
Customercustomer=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<?extendsOptional<?extendsT>>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.
publicOptional<Customer>findById(intid){returnfindByIdLocal(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 ifPresent method on Optional executes a Consumer if the Optional is not empty, as in Example 10-32.
publicvoidprintCustomer(Integerid){findByIdLocal(id).ifPresent(System.out::println);}publicvoidprintCustomers(Integer...ids){Arrays.asList(ids).forEach(this::printCustomer);}
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:
voidifPresentOrElse(Consumer<?superT>action,RunnableemptyAction)
To use it, simply provide a lambda that takes no arguments and returns void, as in Example 10-33.
publicvoidprintCustomer(Integerid){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.
The recipes in Chapter 6 cover the Optional class in Java 8.
Use the new datesUntil method in the LocalDate class, added in Java 9.
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.
publicList<LocalDate>getDays_java8(LocalDatestart,LocalDateend){Periodperiod=start.until(end);returnIntStream.range(0,period.getDays()).mapToObj(start:plusDays).collect(Collectors.toList());}
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:
LocalDatestart=LocalDate.of(2017,Month.JUNE,10);LocalDateend=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:
LocalDatestart=LocalDate.of(2017,Month.JUNE,10);LocalDateend=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.
publicList<LocalDate>getDays_java8(LocalDatestart,LocalDateend){Periodperiod=start.until(end);returnLongStream.range(0,ChronoUnit.DAYS.between(start,end)).mapToObj(start:plusDays).collect(Collectors.toList());}
You can also use the iterate method, but that requires you to know the number of days, as in Example 10-36.
publicList<LocalDate>getDaysByIterate(LocalDatestart,intdays){returnStream.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(LocalDateendExclusive)Stream<LocalDate>datesUntil(LocalDateendExclusive,Periodstep)
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.
publicList<LocalDate>getDays_java9(LocalDatestart,LocalDateend){returnstart.datesUntil(end).collect(Collectors.toList());}publicList<LocalDate>getMonths_java9(LocalDatestart,LocalDateend){returnstart.datesUntil(end,Period.ofMonths(1)).collect(Collectors.toList());}
The datesUntil method produces a Stream, which can be manipulated with all the normal stream processing techniques.
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.