This appendix reviews the main additions to the Java library.
The biggest update to the Collections API is the introduction of streams, which we discussed in chapters 4–6. There are also other updates, which we briefly review in this appendix.
The Java API designers made the most out of default methods and added several new methods to collection interfaces and classes. The new methods are listed in table B.1.
Class/interface |
New methods |
---|---|
Map | getOrDefault, forEach, compute, computeIfAbsent, computeIfPresent, merge, putIfAbsent, remove(key, value), replace, replaceAll |
Iterable | forEach, spliterator |
Iterator | forEachRemaining |
Collection | removeIf, stream, parallelStream |
List | replaceAll, sort |
BitSet | stream |
The Map interface is the most updated interface, with support for several new convenient methods. For example, the method getOrDefault can be used to replace an existing idiom that checks whether a Map contains a mapping for a given key. If not, you can provide a default value to return instead. Previously you would do this:
Map<String, Integer> carInventory = new HashMap<>(); Integer count = 0; if(map.containsKey("Aston Martin")){ count = map.get("Aston Martin"); }
You can now more simply do the following:
Integer count = map.getOrDefault("Aston Martin", 0);
Note that this works only if there’s no mapping. For example, if the key is explicitly mapped to the value null, then no default value will be returned.
Another particularly useful method is computeIfAbsent, which we briefly mentioned in chapter 14 when explaining memoization. It lets you conveniently use the caching pattern. Let’s say that you need to fetch and process data from different websites. In such a scenario, it’s useful to cache the data, so you don’t have to execute the (expensive) fetching operation multiple times:
You can now write this code more concisely by using computeIfAbsent as follows:
public String getData(String url){ return cache.computeIfAbsent(url, this::getData); }
A description of all other methods can be found in the official Java API documentation.[1] Note that ConcurrentHashMap was also updated with additional methods. We discuss them in section B.2.
1 See http://docs.oracle.com/javase/8/docs/api/java/util/Map.html.
The removeIf method can be used to remove all elements in a collection that match a predicate. Note that this is different than the filter method included in the Streams API. The filter method in the Streams API produces a new stream; it doesn’t mutate the current stream or source.
The replaceAll method replaces each element in a List with the result of applying a given operator to it. It’s similar to the map method in a stream, but it mutates the elements of the List. In contrast, the map method produces new elements.
For example, the following code will print [2, 4, 6, 8, 10] because the List is modified in place:
The Collections class has been around for a long time to operate on or return collections. It now includes additional methods to return unmodifiable, synchronized, checked, and empty NavigableMap and NavigableSet. In addition, it includes the method checkedQueue, which returns a view of Queue that’s extended with dynamic type checking.
The Comparator interface now includes default and static methods. You used the Comparator.comparing static method in chapter 3 to return a Comparator object given a function that extracts the sorting key.
New instance methods include the following:
New static methods include these:
Java 8 brings several updates related to concurrency. The first is, of course, the introduction of parallel streams, which we explore in chapter 7. There’s also the introduction of the CompletableFuture class, which you can learn about in chapter 11.
There are other noticeable updates. For example, the Arrays class now supports parallel operations. We discuss these operations in section B.3.
In this section, we look at updates in the java.util.concurrent.atomic package, which deals with atomic variables. In addition, we discuss updates to the Concurrent-HashMap class, which supports several new methods.
The java.util.concurrent.atomic package offers several numeric classes, such as AtomicInteger and AtomicLong that support atomic operation on single variables. They were updated to support new methods:
Here’s how to atomically set the minimum between an observed value of 10 and an existing atomic integer:
int min = atomicInteger.accumulateAndGet(10, Integer::min);
The Java API recommends using the new classes LongAdder, LongAccumulator, Double-Adder, and DoubleAccumulator instead of the Atomic classes equivalent when multiple threads update frequently but read less frequently (for example, in the context of statistics). These classes are designed to grow dynamically to reduce thread contention.
The classes LongAdder and DoubleAdder support operations for additions, whereas LongAccumulator and DoubleAccumulator are given a function to combine values. For example, to calculate the sum of several values, you can use a LongAdder as follows.
Or you can use a LongAccumulator as follows.
The ConcurrentHashMap class was introduced to provide a more modern HashMap, which is concurrent friendly. ConcurrentHashMap allows concurrent add and updates that lock only certain parts of the internal data structure. Thus, read and write operations have improved performance compared to the synchronized Hashtable alternative.
ConcurrentHashMap’s internal structure was updated to improve performance. Entries of a map are typically stored in buckets accessed by the generated hashcode of the key. But if many keys return the same hashcode, performance will deteriorate because buckets are implemented as Lists with O(n) retrieval. In Java 8, when the buckets become too big, they’re dynamically replaced with sorted trees, which have O(log(n)) retrieval. Note that this is possible only when the keys are Comparable (for example, String or Number classes).
ConcurrentHashMap supports three new kinds of operations reminiscent of what you saw with streams:
Each kind of operation supports four forms, accepting functions with keys, values, Map.Entry, and (key, value) arguments:
Note that these operations don’t lock the state of the ConcurrentHashMap. They operate on the elements as they go along. The functions supplied to these operations shouldn’t depend on any ordering or on any other objects or values that may change while computation is in progress.
In addition, you need to specify a parallelism threshold for all these operations. The operations will execute sequentially if the current map size is estimated to be less than the given threshold. Using a value of 1 enables maximal parallelism using the common thread pool. Using a value of Long.MAX_VALUE runs the operation on a single thread.
In this example we use the method reduceValues to find the maximum value in the map:
ConcurrentHashMap<String, Integer> map = new ConcurrentHashMap<>(); Optional<Integer> maxValue = Optional.of(map.reduceValues(1, Integer::max));
Note that there are primitive specializations for int, long, and double for each reduce operation (for example, reduceValuesToInt, reduceKeysToLong, and so on).
The ConcurrentHashMap class provides a new method called mappingCount, which returns the number of mappings in the map as a long. It should be used instead of the method size, which returns an int. This is because the number of mappings may not fit in an int.
The ConcurrentHashMap class provides a new method called keySet that returns a view of the ConcurrentHashMap as a Set (changes to the map are reflected in the Set and vice versa). You can also create a Set backed by a ConcurrentHashMap using the new static method newKeySet.
The Arrays class provides various static methods to manipulate arrays. It now includes four new methods (which have primitive specialized overloaded variants).
The parallelSort method sorts the specified array in parallel, using a natural order, or using an extra Comparator for an array of objects.
The setAll and parallelSetAll methods set all elements of the specified array, respectively sequentially or in parallel, using the provided function to compute each element. The function receives the element index and returns a value for that index. Because parallelSetAll is executed in parallel, the function must be side-effect free, as explained in chapters 7 and 13.
As an example, you can use the method setAll to produce an array with the values 0, 2, 4, 6, ...:
int[] evenNumbers = new int[10]; Arrays.setAll(evenNumbers, i -> i * 2);
The parallelPrefix method cumulates, in parallel, each element of the given array, using the supplied binary operator. In the next listing you produce the values 1, 2, 3, 4, 5, 6, 7, ....
The Java 8 API enhances the Number and Math classes with new methods.
The new methods of the Number class are as follows:
The Math class includes new methods that throw an arithmetic exception if the result of the operation overflows. These methods consist of addExact, subtractExact, multiply-Exact, incrementExact, decrementExact, and negateExact with int and long arguments. In addition, there’s a static toIntExact method to convert a long value to an int. Other additions include the static methods floorMod, floorDiv, and nextDown.
Noticeable additions to the Files class let you produce a stream from files. We mentioned the new static method Files.lines in chapter 5; it lets you read a file lazily as a stream. Other useful static methods that return a stream include the following:
We discussed several changes to the annotation mechanism in Java 8 in appendix A. The Reflection API was updated to support these changes.
Another addition to the Reflection API is that information about parameters of methods such as names and modifiers can now be accessed with the help of the new java.lang.reflect.Parameter class, which is referenced in the new java.lang .reflect.Executable class that serves as a shared superclass for the common functionality of Method and Constructor.
The String class now includes a convenient static method called join to—as you may guess—join strings with a delimiter! You can use it as follows: