Java 8, news and improvements

Previously published on Today Software Magazine.

In the 23rd number of Today Software Magazine we started a discussion about what Java SE8 brings new. Almost unanimously, Java specialists state that the lambda expressions, as a general topic, but also the implications they bring about, represent the most important features of the current version. That is why I thought it useful for the first article to be on this particular topic.

The discussions in this article are carried out at a higher difficulty level, in order to highlight some aspects of performance, productivity and reducing the size of the written code.
For the beginning, I come back to lambda expressions. Through the lambda expressions we can create anonymous methods. However, sometimes, the lambda expressions call methods that already have a name.

In order to clarify things, I would like to go back to the example from the article mentioned in the beginning. We have a Product class with two features, name and price, getters and setters. We will also add to our project a POJO class, where there are different comparing methods, with signatures close to the compare() method of the Comparator interface:

public class ProductComparisons {
 public int compareByName(
   Product a, Product b) {

     return a.getName().
       compareTo(b.getName());
   }

 public int compareByPrice(
   Product a, Product b) {
     return a.getPrice() - 
            b.getPrice();
    }
}

In the test class we will create two Product objects, p1 and p2, which we will place in an array:

Product[] basket = { p1, p2 };

We will sort the array by using the expressions:

ProductComparisons myComparison = 
  new ProductComparisons();

  Arrays.sort(basket, 
   (a,b)->myComparison.  
   compareByName(a, b));

This syntax is possible because the result of the lambda expression is a class marked @FunctionalInterface, in our case, Comparator. Instead of using lambda expressions, Java SE8 offers the possibility of using method references, through the domain operator. The syntax is thus much simplified. I go back to our example and I apply the previous operator. The syntax becomes:

Arrays.sort(basket, 
   myComparison::compareByName);

We have the following types of references:

  • To a method of an object (the previous example).
  • To a static method (the previous example can be easily customised).
  • To a method of an arbitrary object of a particular type:
Arrays.sort(basket, 
   myComparison::compareByName);
  • To a constructor
Supplier s = Product::new;

where the Supplier is from

java.util.function.Supplier;

Another topic I would like to bring to your attention is connected to interfaces. The interfaces used to contain, until this version, only signatures of methods and constants. Java 8 proposes an approach to improve interface usability. If we add new signatures to the interface, then the classes implementing it should be re-written. In order to avoid the process of writing again, the default methods were introduced. Besides signatures and constants, the interfaces will thus contain implementations, too.

I will provide a simple example, to highlight the new approaches:

public interface MyInterface {
	void myClassicMethod();

	default void myDefaultMethod() {
		System.out.println(“hello default!”);
	}
	
	static void myStaticMethod(){
		System.out.println(“hello static!”);
	}
}

Respectively the class implementing the interface:

public class MyClass implements MyInterface {
	@Override
	public void myClassicMethod() {
		System.out.println(“hello classic!”);

	}
}

As a test, we have:

public static void main(String[] args) {
	MyInterface mine = new MyClass();
	mine.myClassicMethod();
	mine.myDefaultMethod();
	MyInterface.myStaticMethod();
}

Thus, we refer to the three behaviors:

  • The predefined one, with the public abstract attribute (myClassicMethod).
  • The default implementation one.
  • The static default implementation one.

The inheritance process also supports this new behavior. Therefore:

  • The default methods which are not mentioned in the derived interface inherit the default behaviour from the base interface.
  • The default methods re-declared in the derived interface become abstract.
  • The default methods redefined in the derived interface will be used overwritten.

I will not conclude this article without describing some of the APIs used in the previous discussions.

java.util.stream, which provides classes for operations on streams of elements. The streams are different from the collections in the following aspects:

  • The stream is not a data structure, but it transforms a data structure in an operation pipeline.
  • An operation on a stream produces an operation, but it does not modify the source.

– the operations on streams are lazy implemented, which means they can expose optimization opportunities.

– the streams are basically infinite. There are also fault (short-circuit) operations that can produce exits/ outcomes in a finite time (limit(), findFirst()).

– the streams are consumable, meaning that the elements are visited only once. For a new visit, one must create a new stream.

  • All the wrapper classes (Boolean, Integer, etc.) have been improved by methods using lambda expressions and references to methods.

The discussions on Java SE8 will be continued in the future editions of TSM. Until then, have a pleasant reading and a beautiful summer!