Java 8 Important Changes

Quang Nguyen
quangtn0018
Published in
4 min readSep 3, 2018

I’ve been dabbling in Java 8 new features and I must admit, they made updates that are definitely useful and should be kept in your tool belt.

First off, let’s talk bout the new changes to Interface.

Default:

Interfaces can now provide a base implementation of a method using the key “default” so classes that implement that interface won’t have to override the default method.

This can be useful when you have a lot of classes implementing the interface, and you need to refer to that default method in each of those classes but now you won’t have to override them which saves a lot of time and headaches.

interface Interface1 {  default void printInterface() {    System.out.println("Interface1");  }}class A implements Interface1 {  public static void main() {    A a = new A();    a.printInterface();    // prints "Interface1"  }}

The only caveat to this is if two interfaces provides the same signature of a default method and a class implements both those interfaces, then you will need to override the default method, or else Java won’t know which one to choose and will complain in the form of a compiler error.

interface Interface1 {  default void printInterface() {    System.out.println("Interface1");  }}interface Interface2 {  default void printInterface() {    System.out.println("Interface2");  }}/*** Compiler Error: [Java] Duplicate default methods named* printInterface with the parameters () and () are inherited* from the types Interface2 and Interface1*/class A implements Interface1, Interface2 {  public static void main() {}}

To fix this:

class A implements Interface1, Interface2 {  public void printInterface() {    System.out.println("Which interface?");  }  public static void main() {    A a = new A();    a.printInterface();    // prints "Which interface?"  }}

Static:

Interfaces can also declare static methods in Java 8. The only caveat to this is if a class implements the interface and tried to override the static method, it will result in a compiler error, so try not to do it.

Second change to Java 8 is the introduction to Functional Interface. These are interfaces that contains exactly one abstract methods (it can also contain other methods, but only one abstract method to be called a function interface).

What’s so special about Function Interface you might ask? Well, it enables Java to join the latest and greatest with Functional Programming Paradigm. It allows for the use of Lambda Expressions.

Lambda expressions are like arrow functions, if you’re familiar with JavaScript.

Lambda expressions allows for:

  1. Reduced lines of code
  2. Sequential and Parallel execution support
  3. Passing behaviors into methods (like JavaScript)
  4. Higher efficiency with laziness (more on this later)

To show you examples of lambda expressions, we must talk about Streams, which is a new API for Java 8.

Stream do not store data. It is a data structure that operate on a source data structure (such as collections or arrays) and produce pipeline data that we can use to perform specific operations on.

Most stream operations must be both non-interfering and stateless.

A function is non-interfering when it doesn’t modify the underlying data structure source on the stream, like adding or removing elements from a collection.

A function is stateless when execution is deterministic, meaning it doesn’t depend on a mutable states from an outer scope.

An example of using a stream:

Stream<Integer> s = Stream.of(1,2,3,4,5);s.map(n -> n*2)
.forEach(System.out::println);
// will print -> 2,4,6,8,10 all in a new line

Notice the lambda expression passed into the map method (looks similar to a arrow function right?).

The weird syntax passed into the forEach method is called method reference. It will reference that method and execute it without having to invoke the method, until it is called to be calculated.

There are two important operations when using streams.

Intermediate operations:

This always returns a stream for us to do additional operations on the source data and it is lazily executed, meaning that it won’t actually pre-compute the values based on the lambda expressions passed into it, until we actually invoke and need those values on the stream.

The main intermediate operations include map and filter, among others that you can find on the Java Doc for streams.

Terminal operations:

This will return either a value or an Optional, which is a safe way of checking whether the computed value that we performed operations on with the stream exist or not. (Because all those null checks in Java is all so common to us).

The main terminal operations includes collect, reduce, forEach, and many more. Again refer to Java Doc.

One essential thing to remember about streams are that they are consumed after executing a terminal operation. So using the example code above, if I tried to refer to the stream s again, it won’t exist because you’ve consumed it so you would have to create the stream again. More specifically, you get an error with something like this java.lang.IllegalStateException: stream has already been operated upon or closed

A solution to this is you can create a Stream Supplier object that you can always refer to:

Supplier<Stream<Integer>> streamSupplier = () -> Stream.of(streamSupplier.get().forEach(System.out::println);
streamSupplier.get()
.map(n -> n*3)
.forEach(System.out::println);
// no errors

I didn’t mention the parallel capabilities of streams so you can look into them on your spare time.

These changes to Java 8 definitely has made Java better, in my opinion and helps to write better code using Java.

--

--