JavaRush /Java Blog /Random EN /Java 8 Features - Ultimate Guide (Part 1)
0xFF
Level 9
Донецк

Java 8 Features - Ultimate Guide (Part 1)

Published in the Random EN group
The first part of the translation of the article Java 8 Features - The ULTIMATE Guide . The second part is here (link may change). Java 8 Features - Ultimate Guide (Part 1) - 1 Editor's Note : The article was published while Java 8 was available to the public and all indications are that it is indeed a major release. Here we have provided Java Code Geeks guides galore, such as Playing With Java 8 - Lambdas and Concurrency , Java 8 Date and Time API Guide: LocalDateTime, and Abstract Class vs. Interface in the Java 8 Era . We also link to 15 must - read Java 8 tutorials from other sources . Of course, we are looking at some of the shortcomings, for example, the dark side of Java 8. So, it's time to collect all the main features of Java 8 in one place for your convenience. Enjoy!

1. Introduction

Without a doubt, the release of Java 8 is the greatest event since Java 5 (released a long time ago, in 2004). It brought many new features to Java, both in the language and in the compiler, libraries, tools, and JVM (Java Virtual Machine). In this tutorial, we're going to take a look at these changes and demonstrate different use cases with real-world examples. The guide consists of several parts, each of which affects a specific side of the platform:
  • Language
  • Compiler
  • Libraries
  • Tools
  • Runtime (JVM)

2. New Features in Java 8

Either way, Java 8 is a major release. We can say that it took so long because of the implementation of the features that 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 private or anonymous methods) are the biggest and most anticipated language change in the entire Java 8 release. They allow us to specify functionality as a method argument (by declaring a function around), or to specify code as data: concepts that every functional developer is familiar with . programming . Many languages ​​on the JVM platform (Groovy, Scala , ...) have had lambdas since day one, but the Java developers had no choice but to represent lambdas via anonymous classes. The discussion about the design of lambdas has taken a lot of time and effort from the public. But in the end, compromises were found, leading to new short designs. In its simplest form, a lambda can be represented as a comma-separated list of parameters, the symbol–> and bodies. For example:
Arrays.asList( "a", "b", "d" ).forEach( e -> System.out.println( e ) )
Note that the type of the e argument is determined by the compiler. In addition, you can explicitly specify the type of a parameter by wrapping the parameter in parentheses. For example:
Arrays.asList( "a", "b", "d" ).forEach( ( String e ) -> System.out.println( e ) );
In case the body of the lambda is more complex, it can be wrapped in curly braces, similar to the definition of a regular function in Java. For example:
Arrays.asList( "a", "b", "d" ).forEach( e -< {
    System.out.print( e );
    System.out.print( e );
} );
The lambda can refer to class members and local variables (implicitly makes the referencing effective whether the finalfield is referenced or not). For example, these 2 snippets are equivalent:
String separator = ",";
Arrays.asList( "a", "b", "d" ).forEach(
    ( String e ) -> System.out.print( e + separator ) );
AND:
final String separator = ",";
Arrays.asList( "a", "b", "d" ).forEach(
    ( String e ) -> System.out.print( e + separator ) );
Lambdas can return a value. The return type will be determined by the compiler. The declaration returnis not required if the body of the lambda is a single line. The two code snippets below are equivalent:
Arrays.asList( "a", "b", "d" ).sort( ( e1, e2 ) -> e1.compareTo( e2 ) );
AND:
Arrays.asList( "a", "b", "d" ).sort( ( e1, e2 ) -> {
    int result = e1.compareTo( e2 );
    return result;
} );
The language designers have been thinking for a long time about how to make existing lambda-friendly functions. As a result, the concept of a functional interface appeared. A functional interface is an interface with only one method. As a result, it can be implicitly converted to a lambda expression. java.lang.Runnableand java.util.concurrent.Callabletwo great examples of functional interfaces. In practice, functional interfaces are very fragile: if one adds even one other method to an interface definition, it will no longer be functional and the compilation process will not complete. To avoid this brittleness and explicitly define the intent of an interface as functional, a special annotation was added in Java 8@FunctionalInterface(all existing interfaces in the Java library have received the @FunctionalInterface annotation). Let's look at this simple definition of a functional interface:
@FunctionalInterface
public interface Functional {
    void method();
}
There is one thing to keep in mind: default methods and static methods do not violate the functional interface principle and can be declared:
@FunctionalInterface
public interface FunctionalDefaultMethods {
    void method();

    default void defaultMethod() {
    }
}
Lambdas are Java 8's most popular item. They have the potential to attract more developers to this great platform and provide smart support for native Java features. For more detailed information refer to the official documentation .

2.2. Default interfaces and static methods

In Java 8, the definition of interfaces has been extended with two new concepts: default method and static method. Default methods make interfaces somewhat like traits, but serve a slightly different purpose. They allow you to add new methods to existing interfaces without breaking backward compatibility for previously written versions of those interfaces. The difference between default methods and abstract methods is that abstract methods must be implemented, but default methods do not. Instead, each interface must provide a so-called default implementation, and all descendants will receive it by default (with the ability to override this default implementation if necessary). Let's look at an example below.
private interface Defaulable {
    // Интерфейсы теперь разрешают методы по умолчанию,
    // клиент может реализовывать  (переопределять)
    // or не реализовывать его
    default String notRequired() {
        return "Default implementation";
    }
}

private static class DefaultableImpl implements Defaulable {
}

private static class OverridableImpl implements Defaulable {
    @Override
    public String notRequired() {
        return "Overridden implementation";
    }
}
The interface Defaulabledeclared a default method notRequired()using the keyword defaultas part of the method definition. One of the classes, DefaultableImpl, implements this interface, leaving the default method as is. Another class, OverridableImpl, overrides the default implementation and provides its own. Another interesting feature introduced in Java 8 is that interfaces can declare (and offer implementations of) static methods. Here is an example:
private interface DefaulableFactory {
    // Interfaces now allow static methods
    static Defaulable create( Supplier<Defaulable> supplier ) {
        return supplier.get();
    }
}
A small piece of code combines the default method and the static method from the example above:
public static void main( String[] args ) {
    Defaulable defaulable = DefaulableFactory.create( DefaultableImpl::new );
    System.out.println( defaulable.notRequired() );

    defaulable = DefaulableFactory.create( OverridableImpl::new );
    System.out.println( defaulable.notRequired() );
}
The console output of this program looks like this:
Default implementation
Overridden implementation
The implementation of default methods in the JVM is very efficient and method invocation is supported by bytecode instructions. The default methods allowed existing Java interfaces to evolve without breaking the compilation process. Good examples are the many methods added to an interface java.util.Collection: stream(), parallelStream(), forEach(), removeIf(), … Although powerful, default methods should be used with care: before declaring a default method, think twice if it's really necessary as it can lead to compilation ambiguities and errors in complex hierarchies. For more information, please refer to the documentation .

2.3. Reference Methods

Reference methods introduce a useful syntax to refer to existing methods or constructors of Java classes or objects (instances). Together with lambda expressions, reference methods make language constructs compact and concise, making it boilerplate. Below is a class Caras an example of different method definitions, let's highlight four supported types of reference methods:
public static class Car {
    public static Car create( final Supplier<Car> supplier ) {
        return supplier.get();
    }

    public static void collide( final Car car ) {
        System.out.println( "Collided " + car.toString() );
    }

    public void follow( final Car another ) {
        System.out.println( "Following the " + another.toString() );
    }

    public void repair() {
        System.out.println( "Repaired " + this.toString() );
    }
}
The first reference method is a constructor reference with syntax Class::newor alternative for generics Class< T >::new. Note that the constructor has no arguments.
final Car car = Car.create( Car::new );
final List<Car> cars = Arrays.asList( car );
The second option is a reference to a static method with the syntax Class::static_method. Note that the method takes exactly one type parameter Car.
cars.forEach( Car::collide );
The third type is a reference to an instance method of an arbitrary object of a certain type with the syntax Class::method. Note that no arguments are accepted by the method.
cars.forEach( Car::repair );
And the last, fourth type is a reference to an instance method of a certain class with the syntax instance::method. Note that the method only takes one parameter of type Car.
final Car police = Car.create( Car::new );
cars.forEach( police::follow );
Running all of these examples as a Java program produces the following console output (class reference Carmay vary):
Collided com.javacodegeeks.java8.method.references.MethodReferences$Car@7a81197d
Repaired com.javacodegeeks.java8.method.references.MethodReferences$Car@7a81197d
Following the com.javacodegeeks.java8.method.references.MethodReferences$Car@7a81197d
For more information and details of reference methods, please refer to the official documentation .

2.4. Repeating annotations

Since Java 5 introduced support for annotations , this feature has become very popular and very widely used. However, one of the limitations of using annotations was the fact that the same annotation cannot be declared more than once in the same place. Java 8 breaks this rule and introduces duplicate annotations. This allows the same annotations to be repeated multiple times where they are declared. Duplicate annotations should annotate themselves using the @Repeatable. In fact, it's not so much a language change as a compiler trick, while the technique remains the same. Let's look at a simple example:
package com.javacodegeeks.java8.repeatable.annotations;

import java.lang.annotation.ElementType;
import java.lang.annotation.Repeatable;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

public class RepeatingAnnotations {
    @Target( ElementType.TYPE )
    @Retention( RetentionPolicy.RUNTIME )
    public @interface Filters {
        Filter[] value();
    }

    @Target( ElementType.TYPE )
    @Retention( RetentionPolicy.RUNTIME )
    @Repeatable( Filters.class )
    public @interface Filter {
        String value();
    };

    @Filter( "filter1" )
    @Filter( "filter2" )
    public interface Filterable {
    }

    public static void main(String[] args) {
        for( Filter filter: Filterable.class.getAnnotationsByType( Filter.class ) ) {
            System.out.println( filter.value() );
        }
    }
}
As we can see, the class Filteris annotated with @Repeatable( Filters. class). Filtersjust the owner of the annotations Filter, but the Java compiler tries to hide their presence from developers. Thus, the interface Filterablecontains annotations Filterthat are declared twice (no mention of Filters). The Reflection API also provides a new method getAnnotationsByType()for returning repeated annotations of some type (remember that Filterable. class.getAnnotation( Filters. class) will return an instance Filtersinjected by the compiler). The output of the program will look like this:
filter1
filter2
See the official documentation for more details .

2.5. Improved type inference

The Java 8 compiler has received many improvements in type inference. In many cases, explicit type parameters can be defined by the compiler, thus making the code cleaner. Let's take a look at one of the examples:
package com.javacodegeeks.java8.type.inference;

public class Value<T> {
    public static<T> T defaultValue() {
        return null;
    }

    public T getOrDefault( T value, T defaultValue ) {
        return ( value != null ) ? value : defaultValue;
    }
}
And here is the usage with type Value<String>:
package com.javacodegeeks.java8.type.inference;

public class TypeInference {
    public static void main(String[] args) {
        final Value<String> value = new Value<>();
        value.getOrDefault( "22", Value.defaultValue() );
    }
}
The type parameter Value.defaultValue()is determined automatically and does not need to be provided explicitly. In Java 7, the same example will not compile and must be rewritten as <NOBR>Value.<String>defaultValue()</NOBR>.

2.6. Extended Annotation Support

Java 8 expands the context where annotations can be used. Now almost everything can have an annotation: local variables, generic types, superclasses and implemented interfaces, even method exceptions. A few examples are presented below:
package com.javacodegeeks.java8.annotations;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import java.util.ArrayList;
import java.util.Collection;

public class Annotations {
    @Retention( RetentionPolicy.RUNTIME )
    @Target( { ElementType.TYPE_USE, ElementType.TYPE_PARAMETER } )
    public @interface NonEmpty {
    }

    public static class Holder<@NonEmpty T> extends @NonEmpty Object {
        public void method() throws @NonEmpty Exception {
        }
    }

    @SuppressWarnings( "unused" )
    public static void main(String[] args) {
        final Holder<String> holder = new @NonEmpty Holder<String>();
        @NonEmpty Collection<@NonEmpty String> strings = new ArrayList<>();
    }
}
ElementType.TYPE_USEand ElementType.TYPE_PARAMETERtwo new element types to describe the corresponding annotation context. Annotation Processing APIhas also undergone minor changes to recognize the new annotation types in Java.

3. New features in the Java compiler

3.1. Parameter names

Throughout time, Java developers have devised various ways to store method parameter names in Java bytecode in order to make them available at runtime (for example, the Paranamer library ). Finally, Java 8 makes this hard work in the language (using the Reflection API and the Parameter.getName()) and bytecode (using the new javac: compiler argument –parameters).
package com.javacodegeeks.java8.parameter.names;

import java.lang.reflect.Method;
import java.lang.reflect.Parameter;

public class ParameterNames {
    public static void main(String[] args) throws Exception {
        Method method = ParameterNames.class.getMethod( "main", String[].class );
        for( final Parameter parameter: method.getParameters() ) {
            System.out.println( "Parameter: " + parameter.getName() );
        }
    }
}
If you compile this class without using an argument –parametersand then run the program, you will see something like this:
Parameter: arg0
With a parameter –parameterspassed to the compiler, the output of the program will be different (the actual name of the parameter will be shown):
Parameter: args
For experienced Maven users, the --parameters argument can be added to the compilation using the maven-compiler-plugin:
<plugin>
    <groupId>org.apache.maven.plugins</groupId>
    <artifactId>maven-compiler-plugin</artifactId>
    <version>3.1</version>
    <configuration>
    <compilerArgument>-parameters</compilerArgument>
    <source>1.8</source>
    <target>1.8</target>
    </configuration>
</plugin>
To check the availability of parameter names, there is a convenient isNamePresent()method provided by the Parameter.

4. New Java Tools

Java 8 comes with a new set of command line tools. In this section, we will consider the most interesting of them.

4.1. Nashorn engine: jjs

jjs is a standalone Nashorn engine which is command line based. It takes a list of JavaScript source files and runs them. For example, let's create a func.js file with the following content:
function f() {
     return 1;
};

print( f() + 1 );
To run this file, let's pass it as an argument to jjs :
jjs func.js
The console output will be like this:
2
See the documentation for more details .

4.2. Class dependency analyzer: jdeps

jdeps is a really great command line tool. It shows package or class level dependencies for Java classes. It takes a .class file, folder, or JAR file as input. By default , jdeps prints dependencies to standard output (console). As an example, let's look at the dependency report of the popular Spring Framework library . To keep the example short, let's look at the dependencies for the JAR file only org.springframework.core-3.0.5.RELEASE.jar.
jdeps org.springframework.core-3.0.5.RELEASE.jar
This command outputs quite a lot, so we will only parse part of the output. Dependencies are grouped by package. If there are no dependencies, not found will be displayed .
org.springframework.core-3.0.5.RELEASE.jar -> C:\Program Files\Java\jdk1.8.0\jre\lib\rt.jar
   org.springframework.core (org.springframework.core-3.0.5.RELEASE.jar)
      -> java.io
      -> java.lang
      -> java.lang.annotation
      -> java.lang.ref
      -> java.lang.reflect
      -> java.util
      -> java.util.concurrent
      -> org.apache.commons.logging                         not found
      -> org.springframework.asm                            not found
      -> org.springframework.asm.commons                    not found
   org.springframework.core.annotation (org.springframework.core-3.0.5.RELEASE.jar)
      -> java.lang
      -> java.lang.annotation
      -> java.lang.reflect
      -> java.util
For more detailed information, please refer to the official documentation .
Comments
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION