Miscellaneous library updates - Java 8 in Action: Lambdas, streams, and functional-style programming (2015)

Java 8 in Action: Lambdas, streams, and functional-style programming (2015)

Appendix B. Miscellaneous library updates

This appendix reviews the main additions to the Java library.

B.1. Collections

The biggest update to the Collections API is the introduction of streams, which we discussed in chapters 46. There are also other updates, which we briefly review in this appendix.

B.1.1. Additional methods

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.

Table B.1. New methods added to collection classes and interfaces

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

Map

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.

Collection

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.

List

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:

B.1.2. The Collections class

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.

B.1.3. Comparator

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:

· reversed—Returns a Comparator with the reverse ordering of the current Comparator.

· thenComparing—Returns a Comparator that uses another Comparator when two objects are equal.

· thenComparingInt, thenComparingDouble, thenComparingLong—Work like the thenComparing method but take a function specialized for primitive types (respectively, ToIntFunction, ToDoubleFunction, and ToLongFunction).

New static methods include these:

· comparingInt, comparingDouble, comparingLong—Work like the comparing method but take a function specialized for primitive types (respectively ToIntFunction, ToDoubleFunction, and ToLongFunction).

· naturalOrder—Returns a Comparator object that imposes a natural order on Comparable objects.

· nullsFirst, nullsLast—Return a Comparator object that considers null to be less than non-null or greater than non-null.

· reverseOrder—Equivalent to naturalOrder().reversed().

B.2. Concurrency

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.

B.2.1. Atomic

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:

· getAndUpdate—Atomically updates the current value with the results of applying the given function, returning the previous value.

· updateAndGet—Atomically updates the current value with the results of applying the given function, returning the updated value.

· getAndAccumulate—Atomically updates the current value with the results of applying the given function to the current and given values, returning the previous value.

· accumulateAndGet—Atomically updates the current value with the results of applying the given function to the current and given values, returning the updated value.

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);

Adders and accumulators

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.

Listing B.1. LongAdder to calculate the sum of values

Or you can use a LongAccumulator as follows.

Listing B.2. LongAccumulator to calculate the sum of values

B.2.2. ConcurrentHashMap

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.

Performance

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 asLists 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).

Stream-like operations

ConcurrentHashMap supports three new kinds of operations reminiscent of what you saw with streams:

· forEach—Performs a given action for each (key, value)

· reduce—Combines all (key, value) given a reduction function into a result

· search—Applies a function on each (key, value) until the function produces a non-null result

Each kind of operation supports four forms, accepting functions with keys, values, Map.Entry, and (key, value) arguments:

· Operates with keys and values (forEach, reduce, search)

· Operates with keys (forEachKey, reduceKeys, searchKeys)

· Operates with values (forEachValue, reduceValues, searchValues)

· Operates with Map.Entry objects (forEachEntry, reduceEntries, searchEntries)

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 ofLong.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).

Counting

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.

Set views

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 methodnewKeySet.

B.3. Arrays

The Arrays class provides various static methods to manipulate arrays. It now includes four new methods (which have primitive specialized overloaded variants).

B.3.1. Using parallelSort

The parallelSort method sorts the specified array in parallel, using a natural order, or using an extra Comparator for an array of objects.

B.3.2. Using setAll and parallelSetAll

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);

B.3.3. Using parallelPrefix

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, ....

Listing B.3. parallelPrefix cumulates in parallel elements of an array

B.4. Number and Math

The Java 8 API enhances the Number and Math classes with new methods.

B.4.1. Number

The new methods of the Number class are as follows:

· The Short, Integer, Long, Float, and Double classes include the sum, min, and max static methods. You saw these methods in conjunction with the reduce operation in chapter 5.

· The Integer and Long classes include the methods compareUnsigned, divideUnsigned, remainderUnsigned, and toUnsignedString to work with unsigned values.

· The Integer and Long classes also respectively include the static methods parse-UnsignedInt and parseUnsignedLong, to parse strings as an unsigned int or long.

· The Byte and Short classes include the methods toUnsignedInt and toUnsigned-Long, to convert the argument to an int or long by an unsigned conversion. Similarly, the Integer class now includes the static method toUnsignedLong.

· The Double and Float classes include the static method isFinite, to check whether the argument is a finite floating-point value.

· The Boolean class now includes the static methods logicalAnd, logicalOr, and logicalXor, to apply the and, or, and xor operations between two booleans.

· The BigInteger class includes the methods byteValueExact, shortValueExact, intValueExact, and longValueExact, to convert this BigInteger to the respective primitive type. But it throws an arithmetic exception if there’s a loss of information during the conversion.

B.4.2. Math

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.

B.5. Files

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:

· Files.list—Produces a Stream<Path> consisting of entries in a given directory. The listing isn’t recursive. Because the stream is consumed lazily, it’s a useful method for processing potentially very large directories.

· Files.walk—Just like Files.list, it produces a Stream<Path> consisting of entries in a given directory. But the listing is recursive and the depth level can be configured. Note that the traversal is performed depth-first.

· Files.find—Produces a Stream<Path> from recursively traversing a directory to find entries that match a given predicate.

B.6. Reflection

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.

B.7. String

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: