Now that you know the basics of lambdas and method references and how they are used in streams, there are several topics that arise from the combination. For example, now that interfaces can have default methods, what happens when a class implements multiple interfaces that have the same default method signature but different implementations? As another example, what happens when you are writing code in a lambda expression and try to access or modify a variable defined outside it? Also, what about exceptions? How are they handled in lambda expressions, where you have no method signature on which to add a throws clause?
This chapter deals with all these issues and more.
You wish to use static utility methods for null checking, comparisons, and more.
One of the lesser-known classes added in Java 7 is the java.util.Objects class, which contains static methods for a variety of tasks. These methods include:
static boolean deepEquals(Object a, Object b)Checks for “deep” equality, which is particularly useful when comparing arrays.
static boolean equals(Object a, Object b)Uses the equals method from the first argument, but is null safe.
static int hash(Object... values)Generates a hash code for a sequence of input values.
static String toString(Object o)Returns the result of calling toString on the argument if not null, and returns null otherwise.
static String toString(Object o, String nullDefault)Returns the result of calling toString on the first argument, and returns the second argument if the first argument is null.
There are also a few overloads of a method useful for validation of arguments:
static <T> T requireNotNull(T obj)Returns T if not null and throws a NullPointerException (NPE) otherwise.
static <T> T requireNotNull(T obj, String message)Same as previous method, but the NPE resulting from a null argument has the specified message.
static <T> T requireNotNull(T obj, Supplier<String> messageSupplier)Same as previous method, but invokes the given Supplier to generate a message for the NPE if the first argument is null.
That last method takes a Supplier<String> as an argument, which finally gives a reason for including this class in a book focused on Java 8 and above. An arguably better reason, however, is given by the isNull and nonNull methods. Each of those returns a boolean:
static boolean isNull(Object obj)Returns true if the provided reference is null and false otherwise.
static boolean nonNull(Object obj)Returns true if the provided reference is not null and false otherwise.
The beauty of these methods is that they can be used as Predicate instances in a filter.
For example, say you have a class that returns a collection. Example 5-1 has a method to return the complete collection, whatever it may be, and a method to return the collection without any nulls.
List<String>strings=Arrays.asList("this",null,"is","a",null,"list","of","strings",null);List<String>nonNullStrings=strings.stream().filter(Objects::nonNull).collect(Collectors.toList());
You can use the Objects.deepEquals method to test this, as in Example 5-2.
@TestpublicvoidtestNonNulls()throwsException{List<String>strings=Arrays.asList("this","is","a","list","of","strings");assertTrue(Objects.deepEquals(strings,nonNullStrings);}
This process can be generalized so that it doesn’t just apply to strings. The code in Example 5-3 filters nulls out of any list.
public<T>List<T>getNonNullElements(List<T>list){returnlist.stream().filter(Objects::nonNull).collect(Collectors.toList());}
Now a method that produces a List with multiple elements being null can be filtered with ease.
Local variables accessed inside lambda expressions must be final or “effectively final.” Attributes can be both accessed and modified.
Back in the late ’90s, when Java was still shiny and new, occasionally developers would write client-side Java applications using the Swing user interface library. Like all GUI libraries, Swing components are event-driven: components generate events and listeners react to them.
Since it was considered a good practice to write separate listeners for each component, listeners were often implemented as anonymous inner classes. This kept them modular, but using inner classes had an added benefit: code in inner classes can access and modify the private attributes of the outer class. For example, a JButton instance generates an ActionEvent, and a ActionListener interface contains a single method called actionPerformed that is invoked once an implementation is registered as a listener. See Example 5-4 for a sample.
publicclassMyGUIextendsJFrame{privateJTextFieldname=newJTextField("Please enter your name");privateJTextFieldresponse=newJTextField("Greeting");privateJButtonbutton=newJButton("Say Hi");publicMyGUI(){// ... unrelated GUI setup code ...Stringgreeting="Hello, %s!";button.addActionListener(newActionListener(){@OverridepublicvoidactionPerformed(ActionEvente){response.setText(String.format(greeting,name.getText());// greeting = "Anything else";}});}}
The greeting string is a local variable defined inside the constructor. The name and response variables are attributes of the class. The ActionListener interface is implemented as an anonymous inner class, whose one method is actionPerformed. Inside the inner class, code can:
Access attributes like name and response
Modify attributes (though that’s not shown here)
Access the local variable greeting
Cannot modify the local variable
In fact, prior to Java 8, the compiler would have required the greeting variable to be declared final. In Java 8, the variable doesn’t have to be declared final, but it must be effectively final. In other words, any code that tries to change the value of a local variable will not compile.
Of course, in Java 8, the anonymous inner class would be replaced by a lambda expression, as in Example 5-5.
Stringgreeting="Hello, %s!";button.addActionListener(e->response.setText(String.format(greeting,name.getText())));
The same rules apply. The greeting variable doesn’t have to be declared to be final, but it must be effectively final or the code will not compile.
If Swing isn’t to your liking, here’s another way to approach this problem. Say you want to sum values in a given List, as in Example 5-6.
List<Integer>nums=Arrays.asList(3,1,4,1,5,9);inttotal=0;for(intn:nums){total+=n;}total=0;nums.forEach(n->total+=n);total=nums.stream().mapToInt(Integer::valueOf).sum()
The code declares a local variable called total. Summing the values using a traditional for-each loop works just fine.
The forEach method defined on Iterable takes a Consumer as an argument, but if the consumer tries to modify the total variable, the code will not compile.
Of course, the right way to solve the problem is to convert the stream into an IntStream, which then has a sum method—no local variables involved at all.
Technically, a function along with the accessible variables defined in its environment is called a closure. By that definition, Java is in somewhat of a gray area—local variables are accessible but cannot be modified. You could argue that Java 8 lambdas are actually closures, in that they are closed over values rather than variables.1
Use the static ints, longs, and doubles methods in java.util.Random.
If all you need is a single random double, the static Math.random method is convenient. It returns a double value between 0.0 and 1.0.2 The process is equivalent to instantiating the java.util.Random class and invoking the nextDouble method.
The Random class provides a constructor that lets you specify a seed. If you specify the same seed, you get the same resulting sequence of random numbers, which can be useful in testing.
If you want a sequential stream of random numbers, however, Java 8 added several methods to the Random class that return them. The methods are ints, longs, and doubles, whose signatures are (without the various overloads):
IntStreamints()LongStreamlongs()DoubleStreamdoubles()
The overloaded versions of each allow you to specify the size of the resulting stream and the min and max values for the generated numbers. For example:
DoubleStreamdoubles(longstreamSize,doublerandomNumberOrigin,doublerandomNumberBound)
The returned stream produces the given streamSize number of pseudorandom double values, each of which is greater than or equal to the randomNumberOrigin and strictly less than the randomNumberBound.
Variants that don’t specify streamSize return an “effectively unlimited” stream of values.
If you don’t specify the min or max, they default to zero and one for doubles, the complete range of Integer for ints, and (effectively) the complete range of Long for longs. In each case, the result is like repeatedly invoking nextDouble, nextInt, or nextLong, respectively.
Example 5-7 shows the sample code.
Randomr=newRandom();r.ints(5).sorted().forEach(System.out::println);r.doubles(5,0,0.5).sorted().forEach(System.out::println);List<Long>longs=r.longs(5).boxed().collect(Collectors.toList());System.out.println(longs);List<Integer>listOfInts=r.ints(5,10,20).collect(LinkedList::new,LinkedList::add,LinkedList::addAll);System.out.println(listOfInts);
The latter two examples deal with the minor annoyance that occurs when you want to create a collection of primitives. You can’t invoke collect(Collectors.toList()) on a collection of primitives, as discussed in Recipe 3.2. As suggested in that recipe, you can either use the boxed method to convert the long values to instances of Long, or you can use the three-argument version of collect and specify the Supplier, accumulator, and combiner yourself, as shown.
It’s worth noting that SecureRandom is a subclass of Random. It provides a cryptographically strong random number generator. All the same methods (ints, longs, doubles, and their overloads) also work on SecureRandom, just with a different generator.
The boxed method in Stream is discussed in Recipe 3.2.
Use one of the many new default methods in the java.util.Map interface, like computeIfAbsent, computeIfPresent, replace, merge, and so on.
The Map interface has been in Java since the rest of the collections framework was added way back in version 1.2. Java 8 introduced default methods in interfaces, and several new methods have been added to Map as a result.
Table 5-1 shows the new methods.
| Method | Purpose |
|---|---|
|
Compute a new value based on the existing key and value |
|
Return the value for the given key if it exists, or use the supplied function to compute and store it if not |
|
Compute a new value to replace an existing value |
|
Iterate over a |
|
If the key exists in the |
|
If the key is not in the |
|
If the key isn’t in the |
|
Remove the entry for this key only if it matches the given value |
|
Replace the existing key with the new value |
|
Replace each entry in the |
That’s a lot of new methods for an interface we’ve been using for over a decade. Some of them are really convenient, however.
The complete signature for the computIfAbsent method is:
VcomputeIfAbsent(Kkey,Function<?superK,?extendsV>mappingFunction)
This method is particularly useful when creating a cache of the results of method calls. For example, consider the classic recursive calculation of Fibonacci numbers. The value of any Fibonacci number greater than 1 is equal to the sum of the previous two Fibonacci numbers,3 as in Example 5-8.
longfib(longi){if(i==0)return0;if(i==1)return1;returnfib(i-1)+fib(i-2);}
The problem is that fib(5) = fib(4) + fib(3) = fib(3) + fib(2) + fib(2) + fib(1) = ... and there are many, many repeated calculations. The way to fix this is to use a cache, a technique known as memoization in functional programming. The result, modified to store BigInteger instances, is shown in Example 5-9.
privateMap<Long,BigInteger>cache=newHashMap<>();publicBigIntegerfib(longi){if(i==0)returnBigInteger.ZERO;if(i==1)returnBigInteger.ONE;returncache.computeIfAbsent(i,n->fib(n-2).add(fib(n-1)));}
The calculation uses a cache where the keys are the supplied numbers and the values are the corresponding Fibonacci numbers. The computeIfAbsent method looks in the cache for a given number. If it exists, it returns the value. Otherwise it uses the supplied Function to compute the new value, store it in the cache, and return it. That’s quite an improvement for a single method.
The complete signature of the computeIfPresent method is:
VcomputeIfPresent(Kkey,BiFunction<?superK,?superV,?extendsV>remappingFunction)
The computeIfPresent method only updates a value if its associated key is already in the map. Consider the case where you are parsing text and making a count of how many times each word appears. Such a calculation, known as a concordance, is not uncommon. If, however, you only care about certain specific keywords, you can use computeIfPresent to update those. See Example 5-10.
publicMap<String,Integer>countWords(Stringpassage,String...strings){Map<String,Integer>wordCounts=newHashMap<>();Arrays.stream(strings).forEach(s->wordCounts.put(s,0));Arrays.stream(passage.split(" ")).forEach(word->wordCounts.computeIfPresent(word,(key,val)->val+1));returnwordCounts;}
By putting the words you care about into the map initially with a count of zero, the computeIfPresent method will only update those values.
If you run this with a passage of text and a comma-separated list of words, as in Example 5-11, you get the results you’re looking for.
Stringpassage="NSA agent walks into a bar. Bartender says, "+"'Hey, I have a new joke for you.' NSA agent says, 'heard it'.";Map<String,Integer>counts=demo.countWords(passage,"NSA","agent","joke");counts.forEach((word,count)->System.out.println(word+"="+count));// Output is NSA=2, agent=2, joke=1
Only the desired words are keys in the map, so only those have their counts updated. As usual, printing the values takes advantage of the default forEach method in Map, which takes a BiConsumer, whose arguments are the keys and the values.
The replace method works like the put method, but only if the key already exists. If not, the replace method does nothing, while put adds a null key, which may not be what you want.
There are two overloads of the replace method:
Vreplace(Kkey,Vvalue)booleanreplace(Kkey,VoldValue,VnewValue)
The first one replaces the value of the key if it exists in the map at all. The second only does the replacement if the current value is the one specified.
The getOrDefault method solves the occasionally annoying fact that calling get on a Map with a key that doesn’t exist returns null. That’s helpful, but the method only returns the default, it doesn’t also add it to the map.
The signature of getOrDefault is:
VgetOrDefault(Objectkey,VdefaultValue)
The getOrDefault method returns the default if the key does not exist in the map, but it does not add the key to the map.
The merge method is very helpful. Its complete signature is:
Vmerge(Kkey,Vvalue,BiFunction<?superV,?superV,?extendsV>remappingFunction)
Say you want the complete word count map for a given passage of text, rather than just the counts for specific words. Normally you would have two conditions: if the word is already in the map, update the count; otherwise put it in the map with a count of one. With merge the process simplifies, as in Example 5-12.
publicMap<String,Integer>fullWordCounts(Stringpassage){Map<String,Integer>wordCounts=newHashMap<>();StringtestString=passage.toLowerCase().replaceAll("\\W"," ");Arrays.stream(testString.split("\\s+")).forEach(word->wordCounts.merge(word,1,Integer::sum));returnwordCounts;}
The merge method takes the key and default value, which is inserted if the key doesn’t already exist in the map. Otherwise merge uses the BinaryOperator (here the sum method in Integer) to compute the new value based on the old value.
Hopefully this recipe makes it clear that the new default methods on Map provide several convenient techniques for your coding.
Implement the method in your class. Your implementation can still use the provided defaults from the interfaces through the super keyword.
Java 8 supports both static and default methods in interfaces. Default methods provide an implementation, which is then inherited by the class. This allows interfaces to add new methods without breaking existing class implementations.
Since classes can implement multiple interfaces, a class may inherit default methods that have the same signature but are implemented differently, or it may already contain its own version of a default method.
There are three possibilities when this occurs:
In any conflict between a method in a class and a default method in an interface, the class always wins.
If the conflict comes between two interfaces where one is a descendant of the other, then the descendant wins, the same way they do in classes.
If there is no inheritance relationship between the two defaults, the class will not compile.
In the last case, simply implement the method in the class and everything will work again. This reduces the third case to the first one.
As an example, consider the Company interface shown in Example 5-13 and the Employee interface shown in Example 5-14.
publicinterfaceCompany{defaultStringgetName(){return"Initech";}// other methods}
The default keyword indicates that the getName method is a default method, which provides an implementation that returns the company name.
publicinterfaceEmployee{StringgetFirst();StringgetLast();voidconvertCaffeineToCodeForMoney();defaultStringgetName(){returnString.format("%s %s",getFirst(),getLast());}}
The Employee interface also contains a default method called getName with the same signature as the one in Company, but with a different implementation. The CompanyEmployee class shown in Example 5-15 implements both interfaces, causing a conflict.
publicclassCompanyEmployeeimplementsCompany,Employee{privateStringfirst;privateStringlast;@OverridepublicvoidconvertCaffeineToCodeForMoney(){System.out.println("Coding...");}@OverridepublicStringgetFirst(){returnfirst;}@OverridepublicStringgetLast(){returnlast;}}
Since CompanyEmployee inherits unrelated defaults for getName, the class won’t compile. To fix this, you need to add your own version of getName to the class, which will then override both the defaults.
You can still use the provided defaults, however, using the super keyword, as shown in Example 5-16.
publicclassCompanyEmployeeimplementsCompany,Employee{@OverridepublicStringgetName(){returnString.format("%s working for %s",Employee.super.getName(),Company.super.getName());}// ... rest as before ...}
In this version, the getName method in the class builds a String from the default versions provided by both Company and Employee.
The best news of all is that this is as complicated as default methods ever get. You now know everything there is to know about them.
Actually, there’s one edge case to consider. If the Company interface contained getName but was not marked default (and didn’t have an implementation, making it abstract), would that still cause a conflict because Employee also had the same method? The answer is yes, interestingly enough, and you still need to provide an implementation in the CompanyEmployee class.
Of course, if the same method appears in both interfaces and neither is a default, then this is the pre-Java 8 situation. There’s no conflict, but the class must provide an implementation.
Default methods in interfaces are discussed in Recipe 1.5.
Rather than using a loop to iterate over a linear collection (i.e., a class that implements Collection or one of its descendants), you can use the new forEach method that has been added to Iterable as a default method.
From the Javadocs, its signature is:
defaultvoidforEach(Consumer<?superT>action)
The argument to forEach is of type Consumer, one of the functional interfaces added to the java.util.function package. A Consumer represents an operation that takes a single generic parameter and returns no result. As the docs say, “unlike most other functional interfaces, Consumer is expected to operate via side effects.”
A pure function operates without side effects, so applying the function with the same parameters always gives the same result. In functional programming, this is known as referential transparency, where a function can be replaced by its corresponding value.
Since java.util.Collection is a subinterface of Iterable, the forEach method is available on all linear collections, from ArrayList to LinkedHashSet. Iterating over each is therefore simple, as Example 5-17 shows.
List<Integer>integers=Arrays.asList(3,1,4,1,5,9);integers.forEach(newConsumer<Integer>(){@Overridepublicvoidaccept(Integerinteger){System.out.println(integer);}});integers.forEach((Integern)->{System.out.println(n);});integers.forEach(n->System.out.println(n));integers.forEach(System.out::println);}
The anonymous inner class version is shown simply as a reminder of the signature of the accept method in the Consumer interface. As the inner class shows, the accept method takes a single argument and returns void. The lambda versions shown are compatible with this. Since each of the lambda versions consists of a single call to the println method on System.out, that method can be used as a method reference, as shown in the last version.
The Map interface also has a forEach method, added as a default. In this case, the signature takes a BiConsumer:
defaultvoidforEach(BiConsumer<?superK,?superV>action)
BiConsumer is another of the new interfaces in the java.util.function package. It represents a function that takes two generic arguments and returns void. When implemented in the forEach method in Map, the arguments become the keys and values from the Map.Entry instances in the entrySet.
That means iterating over a Map is now as easy as iterating over a List, Set, or any other linear collection. Example 5-18 shows the sample code.
Map<Long,String>map=newHashMap<>();map.put(86L,"Don Adams (Maxwell Smart)");map.put(99L,"Barbara Feldon");map.put(13L,"David Ketchum");map.forEach((num,agent)->System.out.printf("Agent %d, played by %s%n",num,agent));
The output from the iteration is shown in Example 5-19.4
Agent 99, played by Barbara Feldon Agent 86, played by Don Adams (Maxwell Smart) Agent 13, played by David Ketchum
Prior to Java 8, to iterate over a Map you needed to first use the keySet or entrySet methods to acquire the Set of keys or Map.Entry instances and then iterate over that. With the new default forEach method, iteration is much simpler.
The functional interfaces Consumer and BiConsumer are discussed in Recipe 2.1.
Use the new logging overloads in the Logger class that take a Supplier.
The logging methods in java.util.logging.Logger, like info, warning, or severe, now have two overloaded versions: one that takes a single String as an argument, and one that takes a Supplier<String>.
For instance, Example 5-20 shows the signatures of the various logging methods.5
voidconfig(Stringmsg)voidconfig(Supplier<String>msgSupplier)voidfine(Stringmsg)voidfine(Supplier<String>msgSupplier)voidfiner(Stringmsg)voidfiner(Supplier<String>msgSupplier)voidfinest(Stringmsg)voidfinest(Supplier<String>msgSupplier)voidinfo(Stringmsg)voidinfo(Supplier<String>msgSupplier)voidwarning(Stringmsg)voidwarning(Supplier<String>msgSupplier)voidsevere(Stringmsg)voidsevere(Supplier<String>msgSupplier)
For each method, the version that takes a String was part of the original API that appeared in Java 1.4. The Supplier version is new to Java 8. If you look at the implementation of the Supplier version in the standard library, you see the code shown in Example 5-21.
publicvoidinfo(Supplier<String>msgSupplier){log(Level.INFO,msgSupplier);}publicvoidlog(Levellevel,Supplier<String>msgSupplier){if(!isLoggable(level)){return;}LogRecordlr=newLogRecord(level,msgSupplier.get());doLog(lr);}
Rather than construct a message that will never be shown, the implementation checks to see if the message will be “loggable.” If the message was provided as a simple string, it would be evaluated whether it was logged or not. The version that uses a Supplier allows the developer to put empty parentheses and an arrow in front of the message, converting it into a Supplier, which will only be invoked if the log level is appropriate. Example 5-22 shows how to use both overloads of info.
privateLoggerlogger=Logger.getLogger(this.getClass().getName());privateList<String>data=newArrayList<>();// ... populate list with data ...logger.info("The data is "+data.toString());logger.info(()->"The data is "+data.toString());
In this example, the message invokes the toString method on every object in the list. In the first case, the resulting string will be formed whether the program shows info messages or not. Converting the log argument to a Supplier by simply adding () -> in front of it means that the get method on the Supplier will only be invoked if the message will be used.
The technique of replacing an argument with a Supplier of the same type is known as deferred execution, and can be used in any context where object creation might be expensive.
Deferred execution is one of the primary use cases for Supplier. Suppliers are discussed in Recipe 2.2.
You want to apply a series of small, independent functions consecutively.
One of the benefits of functional programming is that you can create a set of small, reusable functions that you can combine to solve larger problems. To support this, the functional interfaces in the java.util.function package include methods to make composition easy.
For example, the Function interface has two default methods with the signatures shown in Example 5-23.
default<V>Function<V,R>compose(Function<?superV,?extendsT>before)default<V>Function<T,V>andThen(Function<?superR,?extendsV>after)
The dummy arguments’ names in the Javadocs indicate what each method does. The compose method applies its argument before the original function, while the andThen method applies its argument after the original function.
To demonstrate this, consider the trivial example shown in Example 5-24.
Function<Integer,Integer>add2=x->x+2;Function<Integer,Integer>mult3=x->x*3;Function<Integer,Integer>mult3add2=add2.compose(mult3);Function<Integer,Integer>add2mult3=add2.andThen(mult3);System.out.println("mult3add2(1): "+mult3add2.apply(1));System.out.println("add2mult3(1): "+add2mult3.apply(1));
The add2 function adds 2 to its argument. The mult3 function multiplies its argument by 3. Since mult3add2 is made using compose, first the mult3 function is applied and then the add2 function, whereas for add2mult3 using the andThen function does the opposite.
The results of applying each composite function gives:
mult3add2(1): 5 // because (1 * 3) + 2 == 5 add2mult3(1): 9 // because (1 + 2) * 3 == 9
The result of the composition is a function, so this process creates new operations that can be used later. Say, for example, you receive data as part of an HTTP request, which means it is transmitted in string form. You already have a method to operate on the data, but only if it’s already a number. If this happens frequently, you can compose a function that parses the string data before applying the numerical operation. For instance, see Example 5-25.
Function<Integer,Integer>add2=x->x+2;Function<String,Integer>parseThenAdd2=add2.compose(Integer::parseInt);System.out.println(parseThenAdd2.apply("1"));// prints 3
The new function, parseThenAdd2, invokes the static Integer.parseInt method before adding 2 to the result. Going the other way, you can define a function that invokes a toString method after a numerical operation, as in Example 5-26.
Function<Integer,Integer>add2=x->x+2;Function<Integer,String>plus2toString=add2.andThen(Object::toString);System.out.println(plus2toString.apply(1));// prints "3"
This operation returns a function that takes an Integer argument and returns a String.
The Consumer interface also has a method used for closure composition, as shown in Example 5-27.
defaultConsumer<T>andThen(Consumer<?superT>after)
The Javadocs for Consumer explain that the andThen method returns a composed Consumer that performs the original operation followed by the Consumer argument. If either operation throws an exception, it is thrown to the caller of the composed operation.
See the sample code in Example 5-28.
Loggerlog=Logger.getLogger(...);Consumer<String>printer=System.out::println;Consumer<String>logger=log::info;Consumer<String>printThenLog=printer.andThen(logger);Stream.of("this","is","a","stream","of","strings").forEach(printThenLog);
The example code creates two consumers—one for printing to the console, and one for logging. The composed consumer does both printing and logging for each element of the stream.
The Predicate interface has three methods that can be used to compose predicates, as shown in Example 5-29.
defaultPredicate<T>and(Predicate<?superT>other)defaultPredicate<T>negate()defaultPredicate<T>or(Predicate<?superT>other)
As you might expect the and, or, and negate methods are used to compose predicates using logical and, logical or, and logical not operations. Each returns a composed predicate.
To give a moderately interesting example, consider properties of integers. A perfect square is a number whose square root is also an integer. A triangle number counts the objects that can form an equilateral triangle.6
The code in Example 5-30 shows methods for computing perfect squares and triangle numbers, and how you can use the and method to find numbers that are both.
publicstaticbooleanisPerfect(intx){returnMath.sqrt(x)%1==0;}publicstaticbooleanisTriangular(intx){doubleval=(Math.sqrt(8*x+1)-1)/2;returnval%1==0;}// ...IntPredicatetriangular=CompositionDemo::isTriangular;IntPredicateperfect=CompositionDemo::isPerfect;IntPredicateboth=triangular.and(perfect);IntStream.rangeClosed(1,10_000).filter(both).forEach(System.out::println);
The composition approach can be used to build up complex operations from a small library of simple functions.7
Functions are discussed in Recipe 2.4, consumers in Recipe 2.1, and predicates in Recipe 2.3.
Create a separate method that does the operation, handle the exception there, and invoke the extracted method in your lambda expression.
A lambda expression is effectively the implementation of the single abstract method in a functional interface. As with anonymous inner classes, lambda expressions can only throw exceptions declared in the abstract method signature.
If the required exception is unchecked, the situation is relatively easy. The ancestor of all unchecked exceptions is java.lang.RuntimeException.8 Like any Java code, a lambda expression can throw a runtime exception without declaring it or wrapping the code in a try/catch block. The exception is then propagated to the caller.
Consider, for instance, a method that divides all elements of a collection by a constant value, as shown in Example 5-31.
publicList<Integer>div(List<Integer>values,Integerfactor){returnvalues.stream().map(n->n/factor).collect(Collectors.toList());}
Integer division will throw an ArithmeticException (an unchecked exception) if the denominator is zero.9 This will be propagated to the caller, as shown in Example 5-32.
List<Integer>values=Arrays.asList(30,10,40,10,50,90);List<Integer>scaled=demo.div(values,10);System.out.println(scaled);// prints: [3, 1, 4, 1, 5, 9]scaled=demo.div(values,0);System.out.println(scaled);// throws ArithmeticException: / by zero
The client code invokes the div method, and if the divisor is zero, the lambda expression throws an ArithmeticException. The client can add a try/catch block inside the map method in order to handle the exception, but that leads to some seriously ugly code (see Example 5-33).
publicList<Integer>div(List<Integer>values,Integerfactor){returnvalues.stream().map(n->{try{returnn/factor;}catch(ArithmeticExceptione){e.printStackTrace();}}).collect(Collectors.toList());}
This same process works even for checked exceptions, as long as the checked exception is declared in the functional interface.
It’s generally a good idea to keep stream processing code as simple as possible, with the goal of writing one line per intermediate operation. In this case, you can simplify the code by extracting the function inside map into a method, and the stream processing could be done by calling it, as in Example 5-34.
privateIntegerdivide(Integervalue,Integerfactor){try{returnvalue/factor;}catch(ArithmeticExceptione){e.printStackTrace();}}publicList<Integer>divUsingMethod(List<Integer>values,Integerfactor){returnvalues.stream().map(n->divide(n,factor)).collect(Collectors.toList());}
As an aside, if the extracted method had not needed the factor value, the argument to map could have been simplified to a method reference.
The technique of extracting the lambda to a separate method has benefits as well. You can write tests for the extracted method (using reflection if the method is private), set break points in it, or any other mechanism normally associated with methods.
Lambda expressions with checked exceptions are discussed in Recipe 5.10. Using a generic wrapper method for exceptions is in Recipe 5.11.
Add a try/catch block to the lambda expression, or delegate to an extracted method to handle it.
A lambda expression is effectively the implementation of the single abstract method in a functional interface. A lambda expression can therefore only throw checked exceptions declared in the signature of the abstract method.
Say you are planning to invoke a service using a URL and you need to form a query string from a collection of string parameters. The parameters need to be encoded in a way that allows them to be used in a URL. Java provides a class for this purpose called, naturally enough, java.net.URLEncoder, which has a static encode method that takes a String and encodes it according to a specified encoding scheme.
In this case, what you would like to write is code like Example 5-35.
publicList<String>encodeValues(String...values){returnArrays.stream(values).map(s->URLEncoder.encode(s,"UTF-8"))).collect(Collectors.toList());}
The method takes a variable argument list of strings and tries to run each of them through the UREncoder.encode method under the recommended UTF-8 encoding. Unfortunately, the code does not compile because that method throws a (checked) UnsupportedEncodingException.
You might be tempted to simply declare that the encodeValues method throws that exception, but that doesn’t work (see Example 5-36).
publicList<String>encodeValues(String...values)throwsUnsupportedEncodingException{returnArrays.stream(values).map(s->URLEncoder.encode(s,"UTF-8"))).collect(Collectors.toList());}
The problem is that throwing an exception from a lambda is like building an entirely separate class with a method and throwing the exception from there. It helps to think of the lambda as the implementation of an anonymous inner class, because then it becomes clear that throwing the exception in the inner object still needs to be handled or declared there, not in the surrounding object. Code like that is shown in Example 5-37, which shows both the anonymous inner class version and the lambda expression version.
publicList<String>encodeValuesAnonInnerClass(String...values){returnArrays.stream(values).map(newFunction<String,String>(){@OverridepublicStringapply(Strings){try{returnURLEncoder.encode(s,"UTF-8");}catch(UnsupportedEncodingExceptione){e.printStackTrace();return"";}}}).collect(Collectors.toList());}publicList<String>encodeValues(String...values){returnArrays.stream(values).map(s->{try{returnURLEncoder.encode(s,"UTF-8");}catch(UnsupportedEncodingExceptione){e.printStackTrace();return"";}}).collect(Collectors.toList());}
Since the apply method (the single abstract method from Function) does not declare any checked exceptions, you must add a try/catch block inside any lambda expression that is implementing it. If you use a lambda expression as shown, you don’t even see the apply method signature at all, even if you wanted to modify it (which isn’t allowed anyway).
Example 5-38 shows a version that uses an extracted method for the encoding.
privateStringencodeString(Strings){try{returnURLEncoder.encode(s,"UTF-8");}catch(UnsupportedEncodingExceptione){thrownewRuntimeException(e);}}publicList<String>encodeValuesUsingMethod(String...values){returnArrays.stream(values).map(this::encodeString).collect(Collectors.toList());}
This works, and is simple to implement. It also gives you a method that you can test and/or debug separately. The only downside is that you need to extract a method for each operation that may throw an exception. As mentioned in the previous recipe, however, it often allows for easier testing of the component parts of the stream processing.
Using an extracted method to handle exceptions in lambdas is covered in Recipe 5.9. Using a generic wrapper for exceptions is discussed in Recipe 5.11.
Create special exception classes and add a generic method to accept them and return lambdas without exceptions.
Both Recipes 5.9 and 5.10 show how to delegate to a separate method to handle exceptions thrown from lambda expressions. Unfortunately, you need to define a private method for each operation that may throw an exception. This can be made more versatile using a generic wrapper.
For this approach, define a separate functional interface with a method that declares it throws Exception, and use a wrapper method to connect it to your code.
For example, the map method on Stream requires a Function, but the apply method in Function does not declare any checked exceptions. If you want to use a lambda expression in map that may throw a checked exception, start by creating a separate functional interface that declares that it throws Exception, as in Example 5-39.
@FunctionalInterfacepublicinterfaceFunctionWithException<T,R,EextendsException>{Rapply(Tt)throwsE;}
Now you can add a wrapper method that takes a FunctionWithException and returns a Function by wrapping the apply method in a try/catch block, as shown in Example 5-40.
privatestatic<T,R,EextendsException>Function<T,R>wrapper(FunctionWithException<T,R,E>fe){returnarg->{try{returnfe.apply(arg);}catch(Exceptione){thrownewRuntimeException(e);}};}
The wrapper method accepts code that throws any Exception and builds in the necessary try/catch block, while delegating to the apply method. In this case the wrapper method was made static, but that isn’t required. The result is that you can invoke the wrapper with any Function that throws an exception, as in Example 5-41.
publicList<String>encodeValuesWithWrapper(String...values){returnArrays.stream(values).map(wrapper(s->URLEncoder.encode(s,"UTF-8"))).collect(Collectors.toList());}
Now you can write code in your map operation that throws any exception, and the wrapper method will rethrow it as unchecked. The downside to this approach is that a separate generic wrapper, like ConsumerWithException, SupplierWithException, and so on, is needed for each functional interface you plan to use.
It’s complications like this that make it clear why some Java frameworks (like Spring and Hibernate), and even entire languages (like Groovy and Kotlin), catch all checked exceptions and rethrow them as unchecked.
Lambda expressions with checked exceptions are discussed in Recipe 5.10. Extracting to a method is covered in Recipe 5.9.
1 Why, then, aren’t Java 8 lambdas actually called closures? According to Bruce Eckel, the term “closure” is so heavily overloaded it leads to arguments. “When someone says real closures, it too often means, what closure meant in the first language I encountered with something called closures.” For more information, see his blog post “Are Java 8 Lamdas Closures?”.
2 The Javadocs say the returned values are chosen “pseudorandomly with (approximately) uniform distribution” from that range, which shows the sort of hedging you have to do when discussing random number generators.
3 In the wildly unlikely event that you haven’t already heard this joke: “Rumor has it that this year’s Fibonacci conference is going to be as good as the last two combined.”
4 These examples are taken from the now ancient TV series Get Smart, which ran from 1965 to 1970. Maxwell Smart is essentially a bumbling combination of James Bond and Inspector Clouseau, created by producers Mel Brooks and Buck Henry.
5 You may be wondering why the designers of the Java logging framework didn’t use the same log levels (trace, debug, info, warn, error, and fatal) that every other logging API uses. That’s an excellent question. If you ever find out, please let me know, too.
6 See https://en.wikipedia.org/wiki/Triangular_number for details. Triangle numbers are the number of handshakes needed if each person in a room shakes hands with every other person exactly once.
7 The Unix operating system is based on this idea, with similar advantages.
8 Isn’t that just about the worst-named class in the entire Java API? All exceptions are thrown at runtime; otherwise they’re compiler errors. Shouldn’t that class have been called UncheckedException all along? To emphasize how silly the situation can get, Java 8 also adds a new class called java.io.UncheckedIOException just to avoid some of the issues discussed in this recipe.
9 Interestingly enough, if the values and the divisor are changed to Double instead of Integer, you don’t get an exception at all, even if the divisor is 0.0. Instead you get a result where all the elements are “Infinity.” This, believe it or not, is the correct behavior according to the IEEE 754 specification for handling floating-point values in a binary computer (and caused me massive headaches back when I used to program in—ugh—Fortran; the nightmares have gone away, but it took a while).