This is a note for learning new features of Java 8, 9, 10 and 11. Most contents are based on the Modern Java in Action.

1 Stream

A stream is a sequence of elements from a source that supports data-processing operations. A stream is an immutable data structure whose elements are computed on demand. It can be traversed only once.

1.1 Operations

Stream operations can be classified into two groups: intermediate and terminal. Common intermediate operations are filter, map, limit, sorted, and distinct. Terminal operations are forEach, count and collect.

Use takeWhile and dropWhitle to slice a stream.

Use flatMap(Arrays::stream) to flat streams.

Use anyMatch, allMatch, noneMatch, findAny, findFirst to search a stream.

Use reduce to get a scalar result.

Java introduces three primitive stream interfaces to avoid boxing/unboxing issues: IntStream with mapToInt, DoubleStream with mapToDouble, and LongStream with mapToLong. IntStream and LongStream haos range and rangeClosed methods to generate number streams.

The Collector interface has three main functions:

  • Reducing and summarizing stream elements to a single value
  • Grouping elements
  • Partitioning elements

1.2 Parallel Stream

Use parallel or parallelStream to enable parallel processing. Use sequential to make a parallel stream sequential.

Parallel streams internally use the ForkJoinPool that has as many threads as the value returned by Runtime.getRuntime().availableProcessors(). It can be changed by setting system property java.util.concurrent.ForkJoinPool.common.parallelism.

The main conern of parallel stream is that pure function should be used. Also try to avoid boxing. Use it only in large collection. Don’t use it in LinkedList, Stream.iterate. Use for ArrayList, IntStream.range, LongStream.rang, HashSet, TreeSet.

The fork/join framework implements the ExecutorService interface using ForkJoinPool. It splits a task into smaller tasks and then combine the results. The subtask can be RecursiveTask<R> or RecursiveAction. The RecursiveTask<R> has only one method protected abstract R computer(). The method defines both the logic of task splitting and produce the result of a subtask.

The Spliterator is a splitable iterator that is designed to work in parallel.

2 Module

2.2 Reason of Being

Java has little encapsulation at the package and JAR levels. The Java class path has no notion of versioning and doesn’t support explicit dependencies.

Java module allows control what can be exposed and what can be only used internally.

A module is defined by a module-info.java file. It has three parts:

1
2
3
4
module name {
    exports package-names;
    requires module-names;
}

One module file must be located at the root of the source code hierarchy to let you specify the dpendencies and what to expose. Multiple modules have multiple source hierarchies.

By default, all modules depend on java.base that there is no need to be required explicitly.

Each module can be compiled independently. Any JAR file on the module path without a module-info file becomes an automatic module that implicitly export all their packages. A name for this automatic module is created after its JAR name.

Some fine tune including transitive requires, exports to, open to give reflective access to all packages or opens individually.

uses and provides are used for ServiceLoader.

3 FutureComplatable

Java provides two tool sets for concurrency. One is the Future interface and its CompletableFuture implementation. Another is reactrive programming based on Flow API.

3.1 Concepts

For concurency, early Java had locks (synchronized), Runnable and Thread. Then it came with ExecutorService that decoupled task submission from thread execution. A program provides tasks (Runnable or Callable) to be executed by a thread pool. Callable<T> and Future<T> are result-returning version of Runnable. Then Java 8 provides CompletableFuture that support composing of Future. Java 9 provides reactive Flow API. These two classes support asynchronous call without explict calling join().

One type of concurrency is that threads created by a method call may outlive the call. This is call aynchronous method. It is easy to forget the outstanding thread. The best practice is to keep track of all threads and join them before exiting.

Sleeping or blocking inside a thread pool is harmful because thread is not cheap. Avoid blocking I/O and netwrok calls.

The CompletableFuture exposes exceptions at the time of the get() method and provides exceptionally() to recover from exceptions. In reactive style, an exception callback is used.

The CompletableFuture is so named because it not only supports composing, but also create a Future without giving it any code to run. It has a complete() method allows other thread to complete it later with a value that is availabe for the get() method.

The code a.thenCombine(b, fn) schedules a task for fn until both a and b complete. There is no actual wait operation. Future is one-shot, runs to completion only once.

The mental model for reactive programming is that there are multiple results over time. A publisher calls a subscriber’s onNext() method to send result. A subscriber uses Subscription.request() to make pull-based backpressure.

3.2 CompletableFuture

Before Java 8, it is common to wrap a Future inside a Callable object and submit it to an ExecutorService. To avoid unlimited waiting, provide a timeout argument. CompletableFuture brings the following features with lambda expressions and pipelining:

  • Sequential execution
  • Wating for the completion of all tasks
  • Waiting for the first completed task
  • Completing a Future manually
  • Reaction to a Future completion

The CompletableFuture.supplyAsync() accepts a Supplier argument and returns a CompletableFuture asychrounously. It is executed by one Executor in the ForkJoinPool but can be customized.

The thenApply, thenCompose, thenAccept all have an async version for long-running processing. The allOf() method takes an array of CompletableFuture and returns a CompletableFuture<Void> that’s completed only when all the CompletableFutures passed have completed. Call join() to wait for all to complete.

4 Small Features

4.1 Method as Parameter

Java 8 supports method references that allows a method to be passed as a parameter. It is a shorthand syntax for a lamda express that executes just ONE method. It replaces the lambda expression and, more old fashioned anonyumous class sytax in such a situation.

Four types of methods can be used:

  • ClassName::staticMethod
  • ClassName::instanceMethod
  • objectName::instanceMethod
  • ClassName::new

4.2 Optional

Optional<T> is used to avoid null. Common tasks are:

  • Creating Optional Ojbects: Optional.empty(), Optional.of(car), Optional.ofNullable(car).
  • map() executes an action when a value is present.
  • Use flatMap to flat optional objects.
  • Actions
    • get() return the wrapped value or throw NoSuchElementException.
    • orElse() return a default value if there is no value.
    • orElseGet() return lazilly.
    • or() return optional or lazy get the value.
    • orElseThrow() can throw a different exception.
    • ifPresent() executes the action if a value is present, otherwise, no action is taken.
    • ifPresentOrElse() excutes an action when it is empty.