Best of: Java 8

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!
Table of Contents
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:
// 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:
// 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:
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:
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
:
@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:
// 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.
// 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:
// OLD
myEntity.addListener(new StateListener() {
public void onEvent(String name) {
...
}
);
// NEW
myEntity.addListener(name -> {
...
}
);
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:
// 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:
// 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
:
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-zonejava.time.LocalDateTime
: a date with time but no time-zonejava.time.ZonedDateTime
: a date with time and a time-zonejava.time.Duration
: a time-based amount of timesjava.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.
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.