Best of: Java 8

 · 7 min
Photo by Andrew Welch on Unsplash

In the previous part, we looked at version 7 of Java. Now it’s time to look at its successor, Java 8, a really big update with lots of great new features!


New language features

The general concept of an interface got 2 great enhancements making it so much more possible.

default methods on interfaces

One of the big differences between an interface and an abstract class is the ability to actually design a contract (interface) and design a contract and provide a partial implementation (abstract class) of it.

The disadvantage of an abstract class lies in Java’s single inheritance restriction. So if you want to use a contract with some kind of default implementation AND inherit from another class (abstract or not) you were out of luck… until now!

Interfaces gained the ability to provide default methods, and to implement methods directly in the interface without the need to override it later in a class implementing that interface. This way you can provide additional functionality by just implementing an interface, without any additional code.

In contrast to an abstract class you can’t use any outside resources (except static ones), no constructors, etc. But it will still make it possible to save a lot of code:

java
// OLD  
public interface MyFactory {
    MyEntity build(JSONObject obj);
    List<MyEntity> build(List<JSONObject> objects);  
}

In the implementation we might implement the #build(List) method as a simple for-each-loop with some additional null checks, so why not provide this rudimentary functionality directly in the interface instead, so every implementation benefits from it:

java
// NEW  
public interface MyFactory {

    MyEntity build(JSONObject obj);

    default List<MyEntity> build(List<JSONObject> objects) {  
        if (objects == null) {  
            return Collections.emptyList();
        }

        List<MyEntity> result = new ArrayList<>();  
        for (JSONObject obj : objects) {  
            MyEntity entity = build(obj);  
            if (entity != null) {
                result.add(entity);
            }
        }
        return results;
    }  
}

Moving the code from the implementation to an interface makes sense if you want to reuse the interface and not just for a 1:1 relationship. This way you provide a senseful default implementation for everyone implementing the interface. And they still can override it if they need another behaviour.

Another great aspect of default methods is the possibility to expand the contract provided by the interface without breaking existing implementations:

java
public interface List {

    // PREVIOUS METHODS void add(Object obj);
    Object get(int index);
    boolean isEmpty();

    // NEW ADDED METHODS
    default Object first() {  
        if (isEmpty()) {  
            return null;  
        }  
        return get(0);
    }

    default void sort() {  
        throw new UnsupportedOperationException();  
    }  
}

We added 2 new methods that won’t break any existing implementation. They even get an implemented #first() method for free!

This is actually the way Java 8 extended a lot of interfaces with new behavior, without breaking everyone’s code.

See https://docs.oracle.com/javase/tutorial/java/IandI/defaultmethods.html for more info.

static interface methods

Another addition to interfaces is the ability to add static methods. It’s a little bit like default methods, but they can’t be overridden:

java
public interface MyInterface {

    void add(Object obj);
    Object get(int index);
    boolean isEmpty();

    static void printVersion() {
        System.out.println("0.0.1");
    }  
}

Functional Programming

Lambda expressions and Functional interfaces

One of the greatest additions, especially in combination with the new Stream API, are lambda expressions, making the many steps towards a more functional programming style.

A lambda expression is a function that lives without an associated class and can be passed around like any other object. To make this possible the concept of functional interfaces was created.

If an interface has only a single abstract method, you can implement it like lambda. This rule can be bent by using default methods, as long as you got only one abstract method.

Interfaces with just one abstract method are automatically functional interfaces, but you might also mark these interfaces with the new annotation @java.lang.FunctionalInterface:

java
@FunctionalInterface  
public interface MyFunctionalInterface {  
    void method(String arg0);  
}

Now that we got a functional interface, let’s look at how to use it as a lambda expression. Anonymous classes were the way to go, implementing the abstract methods inline:

java
// OLD  
MyFunctionalInterface oldWay = new MyFunctionalInterface() {  

    public String method(String arg0) {  
        return "First argument: " + arg0;  
    }  
};

Now we can implement a method in a way with less visual noise: (arg0, arg1, ...) -> { return...}.

The parentheses around the arguments are optional if there’s only one argument. And the curly braces and return statement are optional, too, if only a single line of code would occupy the block.

java
// NEW: multiple lines of code  
MyFunctionalInterface newWay = arg0 -> {  
    // ... some other code ...  
    return "First argument: " + arg0);  
};

// NEW: only one line of code  
MyFunctionalInterface newWay = arg0 > "First argument: " + arg0;

Especially in combination with streams (a little down this post), or listeners and predicates, this will make your code way more readable with less typing:

java
// OLD  
myEntity.addListener(new StateListener() {  
    public void onEvent(String name) {  
        ...  
    }  
);

// NEW  
myEntity.addListener(name -&gt; {  
    ...  
    }  
);

See https://docs.oracle.com/javase/tutorial/java/javaOO/lambdaexpressions.html for more infos.

Method references

To make lambdas even better you can also use method references which allows us to shortcut method invocations for easier reading your code.

Instead of calling myInstance.method(arg0) you might use myInstance::method if another method expects an lambda as argument:

java
// OLD  
boolean result = list.forEach(o -> myInstance.method(o));

// NEW  
boolean result = list.forEach(myInstance::method);

We can also call instance methods of the incoming parameters directly via the type:

java
// OLD  
list.stream().filter(str -> String.isEmpty(str));

// NEW  
list.stream().filter(String::isEmpty);

And of course we can use constructors, too, by referencing new:

java
list.stream().map(MyEntityClass::new);

Java Stream API

This is the other big thing in this update that makes our Java developer life much easier. The Java Stream API JSR-335 provides a more functional approach to working with streams of data. Streams are designed to work perfectly with lambda expressions/functional interfaces and method references.

We create a stream by calling java.util.Collection#stream() or #parallelStream(), or java.util.stream.Stream.of(...) (a static method on an interface btw), and use the fluent Stream API to process it in a lazy way.

Chaining processing methods like #filter(...) and #map(...) is creating a processing pipeline without actually processing the items while building it, but returning another stream. To actually process your data you must end the fluent call with a so-called terminal-operation. The Stream API is huge and can’t be sufficiently explained in a single blog post, but here are some methods that might interest you:

non-terminal-operation:

terminal-operation:


Java Time API

Working with dates and time is always cumbersome… the Joda Time dependency fixed most of the annoyances we usually have while using java.util.Date etc.

With Java 8, the java.time-package (JSR-310) was added, which is mostly Joda Time directly in the JDK, so you no longer need an extra dependency for a sensible way to handle date and time.

Instead of representing date and time with java.util.date we now have a lot of different types in our arsenal to represent the different type of dates and times with or without timezones etc.:

  • java.time.LocalDate: a date without time or time-zone
  • java.time.LocalDateTime: a date with time but no time-zone
  • java.time.ZonedDateTime: a date with time and a time-zone
  • java.time.Duration: a time-based amount of times
  • java.time.Instant: a single point on a time-line without a time-zone

You find the whole list in the documentation and you should really check out some other tutorials and guides about the time API, it will make handling dates and times so much easier!


Nashorn JavaScript Engine

A brand-new, implemented from scratch, JavaScript engine called [Nashorn](https://openjdk.java.net/projects/nashorn/) was introduced, replacing its predecessor Rhino. This means better ECMAScript support and better performance.

java
ScriptEngine engine = new ScriptEngine().getEngineByname("nashorn");  
engine.eval("print('Running on nashorn!');");

Don’t celebrate all the JavaScript goodness too early, because Nashorn and its tools are already deprecated with Java 11.


There’s a lot more

As with my previous post about Java 7 I can’t list all the new features of the release, this was just a quick summary of things I find useful. Here’s the overview of all new features.


A Functional Approach to Java Cover Image
Interested in using functional concepts and techniques in your Java code?
Check out my book!
Available in English, Polish, and soon, Chinese.