Java 8 – Examples of the major, new language features

Over the course of the last month or so, I’ve invested some time familiarising myself with the major new features in Java 8, learning how to apply them, and understanding the benefits which can be gained from using them. As part of this exercise I’ve produced some code examples (see below) for the following features:

  • Lambda expressions
  • Streams (aka bulk or aggregate data operations)
  • Default methods
  • Date/time API
  • Optional type

Lambda Expressions

Java 8 now supports functions as first-class types and values, like many other modern languages. A lambda expression represents an anonymous (unnamed) function, that isn’t associated with a class. It has a defined type of a functional interface when used in assignment statements or passed as a method parameter.

The syntax of a lambda expression is like that of a method. For example:

(String s1, String s2) -> s1.length() - s2.length();

As shown in the example above, an expression comprises:

  • Typed params (the left hand side of the ->). If the function takes no params then use an empty set of brackets: ().
  • A body (the right hand side of the ->).
  • A return type. For simple cases you can skip the return statement and the compiler will infer it.
  • A declaration of any thrown exceptions.

Language support for lambda expressions has a number of benefits:

  • Provides a terser way of implementing single abstract method (SAM) than using anonymous inner classes, reducing boilerplate and increasing the readability of code
  • The ability to pass functions as parameters and assign them to variables provides another means of reusing and applying common code.

Streams API

The Streams API is a new concurrency feature which builds on the existing fork-join framework to support parallel processing of data (utilising multi-core CPUs), whilst abstracting the complexity of the underlying multi-threading logic.

A Stream can defined as a sequence of elements from a source that supports both sequential and parallel aggregate (or ‘bulk’) operations. The source of a Stream can include a Collection(s), Arrays, and some I/O classes (e.g. BufferedReader).

The new Streams API, and its companion package of functional interfaces, support aggregating sequences (‘streams’) of elements on-the-fly, concurrently, using higher-level, declarative (‘functional’) style operations such as map, filter and reduce.

The streams concept entails converting a source to a stream, processing the elements in parallel, and then gathering (collecting) the resulting elements into e.g. a Collection. In the case of a Collection, traditional imperative code with external iteration, such as:

List<Student> filteredStudents = new ArrayList<>(students.size());
for (Student student : students) {
  if (student.getDob().getYear() > yearOfBirthFilter) {
    filteredStudents.add(student);
  }
}

can be replaced with:

List<Student> filteredStudents = students.stream()
  .filter(s -> s.getDob().getYear() > yearOfBirthFilter)
  .collect(Collectors.toList());

Using the Stream APIs can offer performance benefits, as streams support both lazy evaluation (they’re not executed the terminal operation is performed) and parallel processing (dividing a task into smaller subtasks (forking), processing the subtasks in parallel across multiple CPU cores, and combining the results (joining) to get the final result).

Default Methods

From Java 8 onwards, an interface can include default implementations for its declared method(s). The compiler uses these ‘default methods’ in classes that implement the interface but which don’t provide their own implementation.

Default methods (aka Virtual Extension or Defender Methods) were primarily added to the language to allow Java APIs to evolve (by adding new methods to existing interfaces) without breaking backwards compatibility. This has allowed many of the existing Collection APIs in Java 8 to be retrofitted with new methods, including support for functional methods using the new Streams API, e.g. java.util.List#spliterator, java.util.Iterator#forEachRemainingjava.util.Map#forEach,  and java.util.Map#getOrDefault.

Java interfaces are still not permitted to have state (due to the complexity that could arise when a class inherits from multiple interfaces). Therefore default methods cannot alter a class’ internal state directly, although they can invoke other (abstract) methods in the same (and possibly other) interface, which could, as well as being able to invoke static methods.

Date/Time API

A new Date and Time API has been added in Java 8, in the java.time package. This new API addresses many of the bug-bears with the existing java.util.Date and java.util.Calendar classes, and related classes such as SimpleDateFormat.

  • Classes are thread-safe. This is mostly achieved by the new classes being immutable.
  • The java.time API is far more consistent and logical in its design than java.util.Date.
  • java.time provides a fluent (method-cascading) API, leading to less and more readable code.

The project (JSR 310) to design the new API was jointly led by the author of Joda-Time (Stephen Colebourne) which itself was an earlier, big improvement over java.util.Date et al. java.time improves upon Joda-Time in many areas, including more time-zone features, and generally making use of core Java language features which are available in more recent versions of Java, e.g. use of enums and support for nanosecond time precision.

Optional

The new class java.util.Optional is a wrapper for a single value which may not be present. It’s a type which explicitly conveys a value might not be present and forces it to be considered in client code at compile time. The type also provides convenience methods for dealing with the case where a value isn’t present, avoiding the need for nested conditional statements to handle null references.

You can also think of an Optional as a Collection of zero (empty) or at most one element. This is consistent with the fact that Optional supports similar aggregate methods (filter, map, flat map) to the Streams API.

java.util.Optional aims to reduce the no. of NullPointerException in code that are caused by developers not handling the possibility that a method may return null. It allows more expressive APIs to be built which indicate that methods may return null and force the null case to be handled.

Code Examples

As mentioned, I’ve produced code examples for each of the above language features. These include comparisons with the equivalent, pre Java 8 code to help identify use-cases for when and where the new features can be applied. You can find the examples on Bitbucket in the ‘java8-examples’ repository.

Summing-up

Java 8 is a very significant release for the language, far more so than Java 7 was. It adds a number of a major new features, the most important ones being Lambda expressions and the Streams API, which together add support for methods as first-class types and bulk / aggregate data operations for Collections, using functional methods (‘filter’, ‘map’ and ‘reduce’). A far better Date/Time API will also be a boon to developer productivity.

Java 8 will help introduce Java developers to functional programming, which in turn will stand them in good stead when learning other, functional programming languages, such as Scala.

Advertisement

Leave a comment

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s