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.
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.
Stream operations can be classified into two groups: intermediate and terminal. Common intermediate operations are
distinct. Terminal operations are
dropWhitle to slice a stream.
flatMap(Arrays::stream) to flat streams.
findFirst to search a stream.
reduce to get a scalar result.
Java introduces three primitive stream interfaces to avoid boxing/unboxing issues:
rangeClosed methods to generate number streams.
Collector interface has three main functions:
- Reducing and summarizing stream elements to a single value
- Grouping elements
- Partitioning elements
1.2 Parallel Stream
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
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
Stream.iterate. Use for
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> has only one method
protected abstract R computer(). The method defines both the logic of task splitting and produce the result of a subtask.
Spliterator is a splitable iterator that is designed to work in parallel.
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:
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
open to give reflective access to all packages or
provides are used for
Java provides two tool sets for concurrency. One is the
Future interface and its
CompletableFuture implementation. Another is reactrive programming based on
For concurency, early Java had locks (
Thread. Then it came with
ExecutorService that decoupled task submission from thread execution. A program provides tasks (
Callable) to be executed by a thread pool.
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
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.
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.
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
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.
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
- Reaction to a
CompletableFuture.supplyAsync() accepts a
Supplier argument and returns a
CompletableFuture asychrounously. It is executed by one
Executor in the
ForkJoinPool but can be customized.
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:
Optional<T> is used to avoid
null. Common tasks are:
- Creating Optional Ojbects:
map()executes an action when a value is present.
flatMapto flat optional objects.
get()return the wrapped value or throw
orElse()return a default value if there is no value.
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.