2. New Features in Java language

Java 8 is by any means a major release. One might say it took so long to finalize in order to implement the features every Java developer was looking for. In this section we are going to cover most of them.

2.1. Lambdas and Functional Interfaces

Lambdas (also known as closures) are the biggest and most awaited language change in the whole Java 8 release. They allow us to treat functionality as a method argument (passing functions around), or treat a code as data: the concepts every functional developer is very familiar with. Many languages on JVM platform (Groovy, Scala, …) have had lambdas since day one, but Java developers had no choice but hammer the lambdas with boilerplate anonymous classes.

Lambdas design discussions have taken a lot of time and community efforts. But finally, the trade-offs have been found, leading to new concise and compact language constructs. In its simplest form, a lambda could be represented as a comma-separated list of parameters, the –> symbol and the body. For example:

  1. Arrays.asList( "a", "b", "d" ).forEach( e -> System.out.println( e ) );

Please notice the type of argument e is being inferred by the compiler. Alternatively, you may explicitly provide the type of the parameter, wrapping the definition in brackets. For example:

  1. Arrays.asList( "a", "b", "d" ).forEach( ( String e ) -> System.out.println( e ) );

In case lambda’s body is more complex, it may be wrapped into square brackets, as the usual function definition in Java. For example:

  1. Arrays.asList( "a", "b", "d" ).forEach( e -> {
  2. System.out.print( e );
  3. System.out.print( e );
  4. } );

Lambdas may reference the class members and local variables (implicitly making them effectively final if they are not). For example, those two snippets are equivalent:

  1. String separator = ",";
  2. Arrays.asList( "a", "b", "d" ).forEach(
  3. ( String e ) -> System.out.print( e + separator ) );

And:

  1. final String separator = ",";
  2. Arrays.asList( "a", "b", "d" ).forEach(
  3. ( String e ) -> System.out.print( e + separator ) );

Lambdas may return a value. The type of the return value will be inferred by compiler. The return statement is not required if the lambda body is just a one-liner. The two code snippets below are equivalent:

  1. Arrays.asList( "a", "b", "d" ).sort( ( e1, e2 ) -> e1.compareTo( e2 ) );

And:

  1. Arrays.asList( "a", "b", "d" ).sort( ( e1, e2 ) -> {
  2. int result = e1.compareTo( e2 );
  3. return result;
  4. } );

Language designers put a lot of thought on how to make already existing functionality lambda-friendly. As a result, the concept of functional interfaces has emerged. The function interface is an interface with just one single method. As such, it may be implicitly converted to a lambda expression. The java.lang.Runnable and java.util.concurrent.Callable are two great examples of functional interfaces. In practice, the functional interfaces are fragile: if someone adds just one another method to the interface definition, it will not be functional anymore and compilation process will fail. To overcome this fragility and explicitly declare the intent of the interface as being functional, Java 8 adds special annotation @FunctionalInterface (all existing interfaces in Java library have been annotated with @FunctionalInterface as well). Let us take a look on this simple functional interface definition:

  1. @FunctionalInterface
    public interface Functional {
    void method();
    }

One thing to keep in mind: default and static methods do not break the functional interface contract and may be declared:

  1. @FunctionalInterface
    public interface FunctionalDefaultMethods {
    void method();

  2. default void defaultMethod() {            
  3. }        
  4. }

Lambdas are the largest selling point of Java 8. It has all the potential to attract more and more developers to this great platform and provide state of the art support for functional programming concepts in pure Java. For more details please refer to official documentation.

2.2. Interface’s Default and Static Methods

Java 8 extends interface declarations with two new concepts: default and static methods. Default methods make interfaces somewhat similar to traits but serve a bit different goal. They allow adding new methods to existing interfaces without breaking the binary compatibility with the code written for older versions of those interfaces.

The difference between default methods and abstract methods is that abstract methods are required to be implemented. But default methods are not. Instead, each interface must provide so called default implementation and all the implementers will inherit it by default (with a possibility to override this default implementation if needed). Let us take a look on example below.

  1. private interface Defaulable {
  2. // Interfaces now allow default methods, the implementer may or
  3. // may not implement (override) them.
  4. default String notRequired() {
  5. return "Default implementation";
  6. }
  7. }
  8.  
  9. private static class DefaultableImpl implements Defaulable {
  10. }
  11.  
  12. private static class OverridableImpl implements Defaulable {
  13. @Override
  14. public String notRequired() {
  15. return "Overridden implementation";
  16. }
  17. }

The interface Defaulable declares a default method notRequired() using keyword default as part of the method definition. One of the classes, DefaultableImpl, implements this interface leaving the default method implementation as-is. Another one, OverridableImpl , overrides the default implementation and provides its own.

Another interesting feature delivered by Java 8 is that interfaces can declare (and provide implementation) of static methods. Here is an example.

  1. private interface DefaulableFactory {
  2. // Interfaces now allow static methods
  3. static Defaulable create( Supplier< Defaulable > supplier ) {
  4. return supplier.get();
  5. }
  6. }

The small code snippet below glues together the default methods and static methods from the examples above.

  1. public static void main( String[] args ) {
  2. Defaulable defaulable = DefaulableFactory.create( DefaultableImpl::new );
  3. System.out.println( defaulable.notRequired() );
  4.  
  5. defaulable = DefaulableFactory.create( OverridableImpl::new );
  6. System.out.println( defaulable.notRequired() );
  7. }

The console output of this program looks like that:

  1. Default implementation
  2. Overridden implementation

Default methods implementation on JVM is very efficient and is supported by the byte code instructions for method invocation. Default methods allowed existing Java interfaces to evolve without breaking the compilation process. The good examples are the plethora of methods added to java.util.Collection interface: stream(), parallelStream(), forEach(), removeIf(), …

Though being powerful, default methods should be used with a caution: before declaring method as default it is better to think twice if it is really needed as it may cause ambiguity and compilation errors in complex hierarchies. For more details please refer to official documentation.

2.3. Method References

Method references provide the useful syntax to refer directly to exiting methods or constructors of Java classes or objects (instances). With conjunction of Lambdas expressions, method references make the language constructs look compact and concise, leaving off boilerplate.

Below, considering the class Car as an example of different method definitions, let us distinguish four supported types of method references.

  1. public static class Car {
  2. public static Car create( final Supplier< Car > supplier ) {
  3. return supplier.get();
  4. }
  5.  
  6. public static void collide( final Car car ) {
  7. System.out.println( "Collided " + car.toString() );
  8. }
  9.  
  10. public void follow( final Car another ) {
  11. System.out.println( "Following the " + another.toString() );
  12. }
  13.  
  14. public void repair() {
  15. System.out.println( "Repaired " + this.toString() );
  16. }
  17. }

The first type of method references is constructor reference with the syntax Class::new or alternatively, for generics, Class< T >::new. Please notice that the constructor has no arguments.

  1. final Car car = Car.create( Car::new );
  2. final List< Car > cars = Arrays.asList( car );

The second type is reference to static method with the syntax Class::static_method. Please notice that the method accepts exactly one parameter of type Car.

  1. cars.forEach( Car::collide );

The third type is reference to instance method of arbitrary object of specific type with the syntax Class::method. Please notice, no arguments are accepted by the method.

  1. cars.forEach( Car::repair );

And the last, fourth type is reference to instance method of particular class instance the syntax instance::method. Please notice that method accepts exactly one parameter of type Car.

  1. final Car police = Car.create( Car::new );
  2. cars.forEach( police::follow );

Running all those examples as a Java program produces following output on a console (the actual Car instances might be different):

  1. Collided com.javacodegeeks.java8.method.references.MethodReferences$Car@7a81197d
  2. Repaired com.javacodegeeks.java8.method.references.MethodReferences$Car@7a81197d
  3. Following the com.javacodegeeks.java8.method.references.MethodReferences$Car@7a81197d

For more examples and details on method references, please refer to official documentation.

2.4. Repeating annotations

Since Java 5 introduced the annotations support, this feature became very popular and is very widely used. However, one of the limitations of annotation usage was the fact that the same annotation cannot be declared more than once at the same location. Java 8 breaks this rule and introduced the repeating annotations. It allows the same annotation to be repeated several times in place it is declared.

The repeating annotations should be themselves annotated with @Repeatable annotation. In fact, it is not a language change but more a compiler trick as underneath the technique stays the same. Let us take a look on quick example:

  1. package com.javacodegeeks.java8.repeatable.annotations;
  2.  
  3. import java.lang.annotation.ElementType;
  4. import java.lang.annotation.Repeatable;
  5. import java.lang.annotation.Retention;
  6. import java.lang.annotation.RetentionPolicy;
  7. import java.lang.annotation.Target;
  8.  
  9. public class RepeatingAnnotations {
  10. @Target( ElementType.TYPE )
  11. @Retention( RetentionPolicy.RUNTIME )
  12. public @interface Filters {
  13. Filter[] value();
  14. }
  15.  
  16. @Target( ElementType.TYPE )
  17. @Retention( RetentionPolicy.RUNTIME )
  18. @Repeatable( Filters.class )
  19. public @interface Filter {
  20. String value();
  21. };
  22.  
  23. @Filter( "filter1" )
  24. @Filter( "filter2" )
  25. public interface Filterable {
  26. }
  27.  
  28. public static void main(String[] args) {
  29. for( Filter filter: Filterable.class.getAnnotationsByType( Filter.class ) ) {
  30. System.out.println( filter.value() );
  31. }
  32. }
  33. }

As we can see, there is an annotation class Filter annotated with @Repeatable( Filters.class ). The Filters is just a holder of Filter annotations but Java compiler tries hard to hide its presence from the developers. As such, the interface Filterable has Filter annotation defined twice (with no mentions of Filters).

Also, the Reflection API provides new method getAnnotationsByType() to return repeating annotations of some type (please notice that Filterable.class.getAnnotation( Filters.class ) will return the instance of Filters injected by the compiler).

The program output looks like that:

  1. filter1
  2. filter2

For more details please refer to official documentation.

2.5. Better Type Inference

Java 8 compiler has improved a lot on type inference. In many cases the explicit type parameters could be inferred by compiler keeping the code cleaner. Let us take a look on one of the examples.

  1. package com.javacodegeeks.java8.type.inference;
  2.  
  3. public class Value< T > {
  4. public static< T > T defaultValue() {
  5. return null;
  6. }
  7.  
  8. public T getOrDefault( T value, T defaultValue ) {
  9. return ( value != null ) ? value : defaultValue;
  10. }
  11. }

And here is the usage of Value< String > type.

  1. package com.javacodegeeks.java8.type.inference;
  2.  
  3. public class TypeInference {
  4. public static void main(String[] args) {
  5. final Value< String > value = new Value<>();
  6. value.getOrDefault( "22", Value.defaultValue() );
  7. }
  8. }

The type parameter of Value.defaultValue()is inferred and is not required to be provided. In Java 7, the same example will not compile and should be rewritten to Value.< String >defaultValue().

2.6. Extended Annotations Support

Java 8 extends the context where annotation might be used. Now, it is possible to annotate mostly everything: local variables, generic types, super-classes and implementing interfaces, even the method’s exceptions declaration. Couple of examples are show below.

  1. package com.javacodegeeks.java8.annotations;
  2.  
  3. import java.lang.annotation.ElementType;
  4. import java.lang.annotation.Retention;
  5. import java.lang.annotation.RetentionPolicy;
  6. import java.lang.annotation.Target;
  7. import java.util.ArrayList;
  8. import java.util.Collection;
  9.  
  10. public class Annotations {
  11. @Retention( RetentionPolicy.RUNTIME )
  12. @Target( { ElementType.TYPE_USE, ElementType.TYPE_PARAMETER } )
  13. public @interface NonEmpty {
  14. }
  15.  
  16. public static class Holder< @NonEmpty T > extends @NonEmpty Object {
  17. public void method() throws @NonEmpty Exception {
  18. }
  19. }
  20.  
  21. @SuppressWarnings( "unused" )
  22. public static void main(String[] args) {
  23. final Holder< String > holder = new @NonEmpty Holder< String >();
  24. @NonEmpty Collection< @NonEmpty String > strings = new ArrayList<>();
  25. }
  26. }

The ElementType.**TYPE_USE and ElementType.**TYPE_PARAMETER are two new element types to describe the applicable annotation context. The Annotation Processing API also underwent some minor changes to recognize those new type annotations in the Java programming language.