Welcome to Java Learning’s ultimate guide to new Java8 features

directory

  • New features of the Java language

    • Lambda expressions

    • Functional interface

    • Method references

    • The default method of the interface

    • Repeated notes

  • New features for the Java compiler

    • Method parameter names can be retrieved by reflection
  • New features of the Java class library

    • Optional

    • Stream

    • Date/Time API (JSR 310)

    • Parallel arrays

    • CompletableFuture

  • New features for the Java Virtual Machine (JVM)

  • conclusion

  • Refer to the article


Title: Ramping up Java Basics Series 21: The ultimate guide to new Java8 features

Categories: – Java Technology – Java Basics tags: – Java8

This is a summary of the new Java8 features. Let’s give these new features a whirl

New features of the Java language

Lambda expressions

Lambda expressions (also known as closures) are the most anticipated change at the Java language level in the entire Java 8 release. Lambda allows functions to be passed as arguments to a method, or code as data: a concept familiar to functional programmers. Many languages on the JVM platform (Groovy, Scala,…) Lambdas have been around since the beginning, but Java programmers have had to replace them with generic anonymous classes.

Discussions about the design of Lambda took up a lot of time and community effort. Happily, a balance has been struck that allows for a new way to construct Lambdas that is both concise and compact. In its simplest form, a lambda can be represented by a comma-separated argument list, a – > symbol, and a function body. Such as:

Arrays.asList( "a"."b"."d" ).forEach( e -> System.out.println( e ) );
Copy the code

Note that the type of parameter e is inferred by the compiler. You can also specify the type of the argument directly by enclosing it in parentheses:

Arrays.asList( "a"."b"."d" ).forEach( ( String e ) -> System.out.println( e ) );
Copy the code

In some cases, lambda functions are more complex, and the body of the function can be enclosed in a pair of curly braces, just as normal functions are defined in Java. Such as:

Arrays.asList( "a"."b"."d").forEach( e -> { System.out.print( e ); System.out.print( e ); });Copy the code

Lambda can refer to member variables and local variables of a class (they are implicitly made final if they are not final, which is more efficient). For example, the following two code fragments are equivalent:

String separator = ",";
Arrays.asList( "a"."b"."d" ).forEach( 
    ( String e ) -> System.out.print( e + separator ) );
Copy the code

And:

final String separator = ",";
Arrays.asList( "a"."b"."d" ).forEach( 
    ( String e ) -> System.out.print( e + separator ) );
Copy the code

Lambda may return a value. The type of the return value is also inferred by the compiler. There is no need to explicitly use a return statement if the body of a lambda function is a single line. The following two code snippets are equivalent:

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

And:

Arrays.asList( "a"."b"."d" ).sort( ( e1, e2 ) -> {
    int result = e1.compareTo( e2 );
    returnresult; });Copy the code

Language designers put a lot of effort into thinking about how to make existing functions lambda-friendly.

The final approach was to add the concept of a functional interface. A functional interface is a plain interface with a method. Interfaces like this can be implicitly converted to lambda expressions.

Java. Lang. Runnable and Java. Util. Concurrent. Callable functional interface is the most typical two examples.

In practice, functional interfaces are error-prone: if someone adds another method to the interface definition, the interface is no longer functional and the compilation fails.

To overcome this vulnerability of functional interfaces and make it clear that interfaces are intended to be functional interfaces, Java8 adds a special annotation, @functionalinterface (all existing interfaces in Java8 are annotated with @functional interface). Let’s look at the definition of this functional interface:

@FunctionalInterface public interface Functional { void method(); } One thing to keep in mind is that default and static methods do not affect the contract of a functional interface and can be used arbitrarily:

@FunctionalInterface public interface FunctionalDefaultMethods { void method();

default void defaultMethod(a) {}Copy the code

} Lambda is Java 8’s biggest selling point. It has the potential to attract more and more programmers to the Java platform and provide an elegant way to support functional programming in a pure Java language environment. Please refer to the official documentation for more details.

Here’s an example:

public class lambdaAnd functional programming{
    @Test
    public void test1(a) {
        List names = Arrays.asList("peter"."anna"."mike"."xenia");

        Collections.sort(names, new Comparator<String>() {
            @Override
            public int compare(String a, String b) {
                returnb.compareTo(a); }}); System.out.println(Arrays.toString(names.toArray())); }@Test
    public void test2(a) {
        List<String> names = Arrays.asList("peter"."anna"."mike"."xenia");

        Collections.sort(names, (String a, String b) -> {
            returnb.compareTo(a); }); Collections.sort(names, (String a, String b) -> b.compareTo(a)); Collections.sort(names, (a, b) -> b.compareTo(a)); System.out.println(Arrays.toString(names.toArray())); }}static void add(double a,String b) {
        System.out.println(a + b);
    }
    @Test
    public void test5(a) {
        D d = (a,b) -> add(a,b);
// interface D {
// void get(int i,String j);
/ /}
        // Add must have the same return type as get, otherwise an error will be reported
// static void add(double a,String b) {
// System.out.println(a + b);
/ /}
    }

    @FunctionalInterface
    interface D {
        void get(int i,String j);
    }
Copy the code

Let’s look at the difference between a Lambda and an anonymous inner class

An anonymous inner class is still a class, but we don’t need to explicitly name it; the compiler will name it automatically. For example, there is code like this:

public class LambdaTest {
    public static void main(String[] args) {
        new Thread(new Runnable() {
            @Override
            public void run(a) {
                System.out.println("Hello World"); } }).start(); }}Copy the code

After compiling, two class files will be generated:

LambdaTest.class
LambdaTest$1.class
Copy the code

Further analysis of LambdaTest. Class bytecode using javap -c LambdaTest. Class, partial results are as follows:

public static void main(java.lang.String[]);
Code:
    0: new           #2                  // class java/lang/Thread
    3: dup
    4: new           #3                  // class com/example/myapplication/lambda/LambdaTest$1
    7: dup
    8: invokespecial #4                  // Method com/example/myapplication/lambda/LambdaTest$1."<init>":()V
    11: invokespecial #5                  // Method java/lang/Thread."
      
       ":(Ljava/lang/Runnable;) V
      
    14: invokevirtual #6                  // Method java/lang/Thread.start:()V
    17: return
Copy the code

You can see that the object with the anonymous inner class was created on line 4: new #3.

For the implementation of Lambda expressions, we will use the above example code to implement Lambda expressions as follows:

public class LambdaTest {
    public static void main(String[] args) {
        new Thread(() -> System.out.println("Hello World")).start(); }}Copy the code

LambdaTest. Class = LambdaTest. Class = LambdaTest. Class = LambdaTest.

public static void main(java.lang.String[]);
Code:
    0: new           #2                  // class java/lang/Thread
    3: dup
    4: invokedynamic #3.0              // InvokeDynamic #0:run:()Ljava/lang/Runnable;
    9: invokespecial #4                  // Method java/lang/Thread."
      
       ":(Ljava/lang/Runnable;) V
      
    12: invokevirtual #5                  // Method java/lang/Thread.start:()V
    15: return
Copy the code

From the above results, we see that the Lambda expression is encapsulated as a private method of the main class and invoked via the Invokedynamic directive.

Therefore, we can conclude that Lambda expressions are implemented using the Invokedynamic instruction, and that writing Lambda expressions does not create a new class.

Since Lambda expressions do not create anonymous inner classes, when the this keyword is used in a Lambda expression, it refers to a reference to an external class.

Functional interface

A functional interface is an interface that has only one abstract method. Note that I’m talking about abstract methods because Java8 added default methods, but a functional interface doesn’t care if there are any default methods in the interface. A normal FunctionalInterface can be annotated using the @functionalinterface annotation to indicate that it is a FunctionalInterface. Whether the annotation is annotated or not has no real effect on the FunctionalInterface, but it is generally recommended, as is the case with the @override annotation.

How do lambda expressions fit into the Java type system? Each lambda corresponds to a given type, represented by an interface. This interface, called a functional interface, must contain only an abstract method declaration. Each lambda expression of that type will be matched to the abstract method. So the default methods are not abstract, and you are free to add default methods to your functional interfaces.

We can use any interface as a lambda expression, as long as the interface contains only one abstract method. To ensure that your interface meets the requirements, you need to add @functionalInterface annotations. The compiler knows about this annotation and will throw a compiler error if you try to add a second abstract method declaration to the interface.

Here are a few examples

public classFunctional interface use{
    @FunctionalInterface
    interface A {
        void say(a);
        default void talk(a) {}}@Test
    public void test1(a) {
        A a = () -> System.out.println("hello");
        a.say();
    }

    @FunctionalInterface
    interface B {
        void say(String i);
    }
    public void test2(a) {
        // The following two methods are equivalent in that they refer to a method through the B interface. Methods can be referenced directly with ::
        B b = System.out::println;
        B b1 = a -> Integer.parseInt("s");// The a in this case could be something else, just passing the method to the interface as its method implementation
        B b2 = Integer::valueOf;// I can be directly substituted with the type of the variable passed to the method
        B b3 = String::valueOf;
        //B b4 = Integer::parseInt; The type does not match and cannot be used

    }
    @FunctionalInterface
    interface C {
        int say(String i);
    }
    public void test3(a) {
        C c = Integer::parseInt;// The method parameters, like the interface method parameters, can be replaced.
        int i = c.say("1");
        // I get an error when I replace the C int with void because the return type is inconsistent.
        System.out.println(i);
        Lambda expressions provide an easy way to pass a method to an interface.
        // A functional interface is an interface that provides only an abstract method injected by lambda expressions without writing an implementation class.
        // There is no need to write anonymous inner classes, which saves a lot of code, such as implementing the Runnable interface.
        // Functional programming means operating on methods as arguments or references. In addition to normal methods, static methods, constructors can also operate in this way.}}Copy the code

Remember that if the @functionalInterface annotation is left out, this code still works.

Method references

Lambda expressions and method references

Now that you have functional interfaces, you can use Lambda expressions and method references. In fact, function descriptors in the table of functional interfaces are Lambda expressions, which are equivalent to anonymous inner classes in functional interfaces. Here’s a simple example:

public class TestLambda {

public static void execute(Runnable runnable) {
    runnable.run();
}
 
public static void main(String[] args) {
    / / Java8 before
    execute(new Runnable() {
        @Override
        public void run(a) {
            System.out.println("run"); }});// Use Lambda expressions
    execute(() -> System.out.println("run"));
}
Copy the code

}

As you can see, Lambda expressions can use less code but be more clearly expressed than using anonymous inner classes. Note that Lambda expressions are not exactly equivalent to anonymous inner classes; the differences lie in the orientation of this and the masking of local variables.

Method references can be thought of as a more concise form of a Lambda expression, using the :: operator. There are three main types of method references:

Method references to static methods (such as Integer's parseInt method, written as Integer::parseInt); Method references to instance methods of any type (e.g., the length method of String, written String::length); A method reference to an instance method of an existing object (for example, if you have a localVariable localVariable that holds an object of type Variable and supports the instance method getValue, write localVariable::getValue).Copy the code

Here’s a simple example of a method reference:

Function<String, Integer> stringToInteger = (String s) -> Integer.parseInt(s);
Copy the code

// Use method references

Function<String, Integer> stringToInteger = Integer::parseInt;
Copy the code

There is also a special form of method reference, constructor reference. If a class has a default constructor, use method references of the form:

Supplier<SomeClass> c1 = SomeClass::new;
SomeClass s1 = c1.get();
Copy the code

/ / equivalent to the

Supplier<SomeClass> c1 = () -> new SomeClass();
SomeClass s1 = c1.get();
Copy the code

If the constructor takes one argument:

Function<Integer, SomeClass> c1 = SomeClass::new;
SomeClass s1 = c1.apply(100);
Copy the code

/ / equivalent to the

Function<Integer, SomeClass> c1 = i -> new SomeClass(i);
SomeClass s1 = c1.apply(100);
Copy the code

The default method of the interface

Java 8 enables us to add non-abstract method implementations to the interface using the default keyword. This feature is also known as Extension Methods. As shown in the following example:

public classThe default method of the interface{
    class B implements A {
// void a(){} Class methods cannot have the same name
    }
    interface A {
        // There can be multiple default methods
        public default void a(a){
            System.out.println("a");
        }
        public default void b(a){
            System.out.println("b");
        }
        Static and default cannot be used at the same time
// public static default void c(){
// System.out.println("c");
/ /}
    }
    public void test(a) {
        B b = newB(); b.a(); }}Copy the code

The reason for default methods is to extend the original interface, so that you don’t have to change the original interface and cause code incompatibilities in programs that already use those interfaces. Java8 also added default methods for some interfaces, such as the Map interface, and so on. In general, there are two scenarios where the default method is used: optional methods and multiple inheritance of behavior.

Default methods are relatively simple to use, and the only thing to watch out for is how to handle default method conflicts. Here are three rules for handling default method conflicts:

A method in a class has the highest priority. A method declared in a class or parent class takes precedence over any method declared as the default.

If you cannot follow the first rule, the subinterface takes precedence: the interface with the most implementation-specific default method is preferred if the function signature is the same. If B inherits FROM A, then B is more concrete than A.

Finally, classes that inherit multiple interfaces must explicitly choose which implementation of the default method to use by explicitly overwriting and calling the desired method, if it is still impossible to determine. So how to specify explicitly:

public class C implements B.A {
 
    public void hello(a) {
        B.super().hello(); }}Copy the code

Using X.s uper. M (..) Explicitly invoke the desired method.

Java 8 extends the declaration of interfaces with the new concepts of default and static methods. The default approach makes interfaces a bit like Traits (Traits in Scala are similar to Interfaces in Java, but can contain implementation code, which is now a new feature in Java8), but a bit different from traditional interfaces in that it allows new methods to be added to existing interfaces. While maintaining compatibility with older versions of code.

The default method differs from the abstract method in that the abstract method must require implementation, but the default method does not. Instead, each interface must provide a so-called default implementation, which all interface implementers will inherit by default (and override if necessary). Let’s look at the following example:

private interface Defaulable {
    // Interfaces now allow default methods, the implementer may or 
    // may not implement (override) them.
    default String notRequired(a) { 
        return "Default implementation"; }}private static class DefaultableImpl implements Defaulable {}private static class OverridableImpl implements Defaulable {
    @Override
    public String notRequired(a) {
        return "Overridden implementation"; }}Copy the code

The Defaulable interface declares a default method notRequired() with the keyword default, which is implemented by one of the implementers of the Defaulable interface, DefaultableImpl, and leaves the default method as it is. OverridableImpl, another implementer of the Defaulable interface, overrides the default method with its own method.

Another interesting feature that Java 8 brings is that interfaces can declare (and provide implementation) static methods. Such as:

private interface DefaulableFactory {
    // Interfaces now allow static methods
    static Defaulable create( Supplier< Defaulable > supplier ) {
        returnsupplier.get(); }}Copy the code

The following code snippet blends the above default method with the static method.

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() );
}
Copy the code

The console output of this program is as follows:

In the JVM, the implementation of Default methods is very efficient and provides support for method invocation through bytecode instructions. The default method allows existing Java interfaces to continue to be used while maintaining normal compilation. A good example of this is the number of methods added to the java.util.collection interface: stream(), parallelStream(), forEach(), removeIf()…

While default methods are powerful, there is one thing to be careful about when using them: Before declaring a default method, think carefully about whether it is really necessary to use them because they introduce ambiguity and are prone to compile errors in complex inheritance systems. Please refer to the official documentation for more details

Repeated notes

Since Java 5 introduced annotations, this feature has become very popular and widely used. However, one limitation of using annotations is that the same annotation can only be declared once in the same place, not more than once. Java 8 breaks this rule by introducing repeated annotations so that the same annotations can be declared multiple times in the same place.

The Repeatable annotation mechanism itself must be annotated with @REPEATable. In fact, this is not a language change, but more of a compiler trick, and the underlying principles remain the same. Let’s look at a quick start 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(a);
    };
     
    @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() ); }}}Copy the code

As we can see, there is an annotation class Filter that uses the @REPEATable (Filters.class) annotation. Filters are just an array of Filters annotations, but the Java compiler doesn’t want the programmer to be aware of Filters. Thus, the interface Filterable has two Filter annotations (without mentioning Filter).

At the same time, Reflect this API provides a new function getAnnotationsByType () to return to repeat the annotation type (please note Filterable. Class. GetAnnotation (Filters. The class The compiler will return an instance of the Filters after processing.

The program output is as follows:

Filter1 Filter2 Refer to the official documentation for more details

New features for the Java compiler

Method parameter names can be retrieved by reflection

For a long time, Java programmers have been inventing different ways to keep the names of method parameters in Java bytecode and get them at run time (for example, the Paranamer library). Finally, in Java 8, this much-requested functionality was added to the language level (via reflection APIS and parameter.getName () methods) and bytecode files (via the -parameters option in a new version of Javac).

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 the class without the -parameters parameter and then run the class, you get the following output:

If you compile this class with the -parameters Parameter, the structure of the program will be different (the actual name of the Parameter will be displayed) :

Parameter: args

New features of the Java class library

Java 8 improves support for concurrent programming, functional programming, date/time operations, and much more by adding lots of new classes and extending the functionality of existing classes.

Optional

The notorious null-pointer exception is by far the most common cause of Java application failure. In the past, Google’s famous Guava project introduced the Optional class to address null pointer exceptions. Guava encourages programmers to write cleaner code by checking for null values to prevent code contamination. Inspired by Google Guava, the Optional class has become part of the Java 8 class library.

Optional is actually a container: it can hold values of type T, or just NULL. Optional provides a number of useful methods so that we don’t have to explicitly null-check. Please refer to the official documentation for more details.

Let’s use two small examples to demonstrate how to use the Optional class: one that allows null values and one that does not.

public classNull pointerOptional {
    public static void main(String[] args) {

        // The null pointer exception is still reported with the of method
// Optional optional = Optional.of(null);
// System.out.println(optional.get());

        // Throw an exception without the element
        //Exception in thread "main" java.util.NoSuchElementException: No value present
// at java.util.Optional.get(Optional.java:135)
// at com.javase.java8.null pointer option. main(null pointer option.java :14)
// Optional optional1 = Optional.ofNullable(null);
// System.out.println(optional1.get());
        Optional optional = Optional.ofNullable(null);
        System.out.println(optional.isPresent());
        System.out.println(optional.orElse(0));// Give the initial value when the value is null
        System.out.println(optional.orElseGet(() -> new String[]{"a"}));// Use the callback function to set the default value
        The optional.ispresent () method does not raise a null pointer exception even if the element passed in Optional is empty
        // So you can write code that avoids null-pointer exceptions with optional.orElse
        // Print optional. empty.}}Copy the code

IsPresent () returns true if an instance of Optional is non-null, and false if not. To prevent Optional from being null, the orElseGet() method uses a callback function to generate a default value. The map() function converts the value of the current Optional and returns a new Optional instance. The orElse() method is similar to the orElseGet() method, but orElse accepts a default value instead of a callback function. Here is the output of the program:

Full Name is set? false Full Name: [none] Hey Stranger! Let’s look at another example:

Optional< String > firstName = Optional.of( "Tom" );
System.out.println( "First Name is set? " + firstName.isPresent() );        
System.out.println( "First Name: " + firstName.orElseGet( () -> "[none]")); System.out.println( firstName.map( s ->"Hey " + s + "!" ).orElse( "Hey Stranger!")); System.out.println();Copy the code

Here is the output of the program:

First Name is set? true First Name: Tom Hey Tom!

Stream

The recent addition of the Stream API (java.util.stream) brings a true functional programming style to Java. This is by far the best addition to the Java class library, because the Stream API provides a great deal of productivity for Java programmers, allowing them to write efficient, clean, and concise code.

The Stream API greatly simplifies collection framework processing (but its scope is not limited to collection framework processing, as we’ll see later). Let’s take a simple Task class as an example:

The Task class has a concept of score (or pseudo-complexity), followed by a state where the value can be OPEN or CLOSED. Let’s introduce a small collection of tasks as an example:

final Collection< Task > tasks = Arrays.asList(
    new Task( Status.OPEN, 5 ),
    new Task( Status.OPEN, 13 ),
    new Task( Status.CLOSED, 8));Copy the code

So the first question we’re going to talk about is how many points are there for all the tasks that are in the OPEN state? Before Java 8, the usual solution was foreach loops, but in Java 8 we can use stream: a string of elements that supports continuous, parallel aggregation operations.

// Calculate total points of all active tasks using sum()
final longtotalPointsOfOpenTasks = tasks .stream() .filter( task -> task.getStatus() == Status.OPEN ) .mapToInt( Task::getPoints )  .sum(); System.out.println("Total points: " + totalPointsOfOpenTasks );
Copy the code

The output of the program on the console is as follows:

Total points: 18

There are a few caveats.

First, the task collection is transformed into its corresponding Stream representation. The filter operation then filters out the CLOSED tasks.

Next, the mapToInt operation calls the getPoints method of each Task instance to convert the Task stream to an Integer stream by calling Task::getPoints. Finally, use the sum function to add up all the fractions to get the final result.

Before continuing with the examples below, there are a few things to note about streams. the stream operations are divided into intermediate and final operations.

The intermediate operation returns a new stream object. The intermediate operation is always lazy-evaluated, and running an intermediate operation like filter does not actually do any filtering. Instead, it iterates through the elements and produces a new stream object that contains all the elements in the original stream that fit the given predicate.

Final operations like forEach, sum, and so on May directly traverse the stream, producing a result or side effect. When the final operation is completed, the Stream pipe is considered consumed and not likely to be used again. In most cases, the final operation is early evaluation, early completion of the traversal of the underlying data source.

Another valuable aspect of Stream is its ability to support parallel processing natively. Let’s take a look at the task score sum example.

Another valuable aspect of Stream is its ability to support parallel processing natively. Let’s take a look at the task score sum example.

// Calculate total points of all tasks
final double totalPoints = tasks
   .stream()
   .parallel()
   .map( task -> task.getPoints() ) // or map( Task::getPoints ) 
   .reduce( 0, Integer::sum );
     
System.out.println( "Total points (all tasks): " + totalPoints );
Copy the code

This example is similar to the first, except that the program is run in parallel, and the reduce method is used to calculate the final result. Here is the output of this example on the console:

Total Points (all Tasks): 26.0 There is often a requirement that we need to group the elements of a collection according to some criteria. A Stream can also handle such requirements. Here is an example:

// Group tasks by their status
final Map< Status, List< Task > > map = tasks
    .stream()
    .collect( Collectors.groupingBy( Task::getStatus ) );
System.out.println( map );
Copy the code

The console output for this example is as follows:

{CLOSED=[[CLOSED, 8]], OPEN=[[OPEN, 5], [OPEN, 13]]} Let’s finish the task example by calculating the average of each task score (or weight) across the collection.

// Calculate the weight of each tasks (as percent of total points) 
final Collection< String > result = tasks
    .stream()                                        // Stream< String >
    .mapToInt( Task::getPoints )                     // IntStream
    .asLongStream()                                  // LongStream
    .mapToDouble( points -> points / totalPoints )   // DoubleStream
    .boxed()                                         // Stream< Double >
    .mapToLong( weigth -> ( long )( weigth * 100))// LongStream
    .mapToObj( percentage -> percentage + "%" )      // Stream< String> 
    .collect( Collectors.toList() );                 // List< String > 
         
System.out.println( result );
Copy the code

Here is the console output for this example:

Finally, as mentioned earlier, the Stream API doesn’t just deal with Java collections frameworks. Typical I/O operations, such as reading data line by line from a text file, are also well handled by the Stream API. Here is an example to prove this point.

final Path path = new File( filename ).toPath();
try( Stream< String > lines = Files.lines( path, StandardCharsets.UTF_8 ) ) {
    lines.onClose( () -> System.out.println("Done!") ).forEach( System.out::println );
}
Copy the code

Calling onClose on a stream returns a stream with the closing function added to the original function. When close() is called on a stream, the handler associated with the closing is executed.

The Stream API, Lambda expressions, and method references, combined with interface default methods and static methods, are Java 8’s response to the modern software development paradigm. Please refer to the official documentation for more details.

Date/Time API (JSR 310)

Java 8 further enhances Date and Time handling with the release of a new date-time API (JSR 310). Manipulating dates and times has long been one of the most painful aspects of Java programming. The standard java.util.date and later Java.util.Calendar do nothing to improve the situation (they are somewhat more complex, so to speak).

This led directly to the creation of Joda-time, a very powerful Java API that replaces standard date/Time processing. Java 8’s new date-time API (JSR 310) is heavily influenced by, and draws on, Joda-time. The new java.time package covers all operations that handle date, time, date/time, time zone, instants, during, and clock. When designing the new API, a great deal of attention was paid to compatibility with the older API: no changes were allowed (a profound lesson learned from java.util.calendar). If modification is required, a new instance of the class is returned.

Let’s take a look at how the new API’s main classes are used with examples. The first is the Clock class, which gets the current time, date, and time by specifying a time zone. Clock can replace System.currentTimemillis () and timezone.getDefault ().

// Get the system clock as UTC offset 
final Clock clock = Clock.systemUTC();
System.out.println( clock.instant() );
System.out.println( clock.millis() );
Copy the code

Here is the output from the program on the console:

2014-04-12T15:19:29.282Z
1397315969360
Copy the code

The other classes we need to focus on are LocaleDate and LocalTime. LocaleDate holds only the date part in ISO-8601 format with no time zone information. Accordingly, LocaleTime only holds the time part in ISO-8601 format with no time zone information. LocaleDate and LocalTime are both available from Clock.

// Get the local date and local time
final LocalDate date = LocalDate.now();
final LocalDate dateFromClock = LocalDate.now( clock );
         
System.out.println( date );
System.out.println( dateFromClock );
         
// Get the local date and local time
final LocalTime time = LocalTime.now();
final LocalTime timeFromClock = LocalTime.now( clock );
     
System.out.println( time );
System.out.println( timeFromClock );
Copy the code

Here is the output from the program on the console:

2014-04-12
2014-04-12
11:25:54.568
15:25:54.568
Copy the code

Here is the output from the program on the console:

2014-04-12T11:47:01.017-04:00[America/New_York]
2014-04-12T15:47:01.017Z
2014-04-12T08:47:01.017-07:00[America/Los_Angeles]
Copy the code

Finally, let’s look at the Duration class: a period of time at the second and nanosecond levels. Duration makes it easy to calculate the difference between two days. Let’s look at an example of this.

// Get duration between two dates
final LocalDateTime from = LocalDateTime.of( 2014, Month.APRIL, 16.0.0.0 );
final LocalDateTime to = LocalDateTime.of( 2015, Month.APRIL, 16.23.59.59 );
 
final Duration duration = Duration.between( from, to );
System.out.println( "Duration in days: " + duration.toDays() );
System.out.println( "Duration in hours: " + duration.toHours() );
Copy the code

The above example computes the process between April 16, 2014 and April 16, 2014. Here is the output from the program on the console:

Duration in Days: 365 Duration in Hours: 8783 Overall impressions of Java 8’s date/time API improvements are very, very good. Partly because it was built on the Joda-time of Long Battle, and partly because it took a lot of Time to design it, and this Time the programmer’s voice was recognized. Please refer to the official documentation for more details.

Parallel arrays

Java 8 adds a number of new methods for parallel processing of arrays. Arguably the most important is the parallelSort() method, because it can greatly speed up array sorting on multicore machines. The following example shows the use of the new method (parallelXxx).

package com.javacodegeeks.java8.parallel.arrays;
 
import java.util.Arrays;
import java.util.concurrent.ThreadLocalRandom;
 
public class ParallelArrays {
    public static void main( String[] args ) {
        long[] arrayOfLong = new long [ 20000 ];        
         
        Arrays.parallelSetAll( arrayOfLong, 
            index -> ThreadLocalRandom.current().nextInt( 1000000)); Arrays.stream( arrayOfLong ).limit(10 ).forEach( 
            i -> System.out.print( i + "")); System.out.println(); Arrays.parallelSort( arrayOfLong ); Arrays.stream( arrayOfLong ).limit(10 ).forEach( 
            i -> System.out.print( i + "")); System.out.println(); }}Copy the code

The code snippet above uses the parallelSetAll() method to randomly assign values to an array of 20,000 elements. The parallelSort method is then called. The program first prints out the values of the first 10 elements, then sorts the entire array. The output of this program on the console looks like this (note that the array elements are produced randomly) :

Unsorted: 591217 891976 443951 424479 766825 351964 242997 642839 119108 552378 Sorted: 39 220 263 268 325 607 655 678 723 793

CompletableFuture

Before Java8, we used the Future interface provided by the JDK for asynchronous operations. CompletableFuture implements the Future interface and uses ForkJoinPool to perform tasks, so essentially, CompletableFuture is just a wrapper around the old API, and the difference between using CompletableFuture and the old Future is that you can combine two futures, or if two futures are dependent, You can wait for the first to complete before implementing the second equivalent feature.

Let’s take a look at the basic usage:

public Future<Double> getPriceAsync(final String product) {
    final CompletableFuture<Double> futurePrice = new CompletableFuture<>();
    new Thread(() -> {
        double price = calculatePrice(product);
        futurePrice.complete(price);  // Set the return value of the future using the complete method
    }).start();
    return futurePrice;
}
Copy the code

Once you get the Future, you can use the get method to get the result. CompletableFuture provides factory methods to simplify these apis and use them in a functional programming way, such as:

Fufure price = CompletableFuture.supplyAsync(() -> calculatePrice(product)); Isn’t the code a lot cleaner all of a sudden? As I said, completableFutures can combine multiple futures, whether they have dependencies or no dependencies.

If the second request depends on the result of the first request, then the thenCompose method can be used to combine the two futures

public List<String> findPriceAsync(String product) {
    List<CompletableFutute<String>> priceFutures = tasks.stream()
    .map(task -> CompletableFuture.supplyAsync(() -> task.getPrice(product),executor))
    .map(future -> future.thenApply(Work::parse))
    .map(future -> future.thenCompose(work -> CompletableFuture.supplyAsync(() -> Count.applyCount(work), executor)))
    .collect(Collectors.toList());

    return priceFutures.stream().map(CompletableFuture::join).collect(Collectors.toList());
}
Copy the code

The code above uses thenCompose to combine two CompletableFutures. The second argument to the supplyAsync method accepts a custom Executor. First perform a task with CompletableFuture, call the getPrice method to get a Future, then use thenApply to apply the result of the Future to the parse method. The applyCount method is then executed using the parse result as an argument, and a List of CompletableFuture is collected. Finally, a stream is used to call the Join method of the CompletableFuture. This is to wait for all asynchronous tasks to complete and get the final result.

Note that you must use two streams. If you call the JOIN method in one Stream, all operations will be executed serially, not asynchronously, due to the delayed nature of the Stream.

Let’s look at another example where there is no dependency between two futures:

The Future < String > futurePriceInUsd = CompletableFuture. SupplyAsync (() - > shop. GetPrice (" price1 ")) . ThenCombine (CompletableFuture. SupplyAsync (() - > shop. GetPrice (" price2 ")), (s1, s2) - > s1 + s2);Copy the code

Here are two asynchronous tasks that combine two futures using thenCombine. ThenCombine’s second argument is the operation function used to combine the return values of the two Future methods.

Sometimes, instead of waiting for all the asynchronous tasks to finish, we just need one of them to complete, and CompletableFuture also provides this method:

// Suppose the getStream method returns a Stream
      
       >
      CompletableFuture[] futures = getStream(" listen ").map(f -> f.thenAccept(System.out::println)).toArray(CompletableFuture[]::new);
// Wait for one of them to completeCompletableFuture.anyOf(futures).join(); Use the anyOf method to respond to the Completion event for the CompletableFuture.Copy the code

New features for the Java Virtual Machine (JVM)

The PermGen space was removed and replaced with Metaspace (JEP 122). JVM options -xx :PermSize and -xx :MaxPermSize were replaced by -xx :MetaSpaceSize and -xx :MaxMetaspaceSize, respectively.

conclusion

More outlook: Java 8 advances this great platform by releasing features that increase programmer productivity. It is too early to move production environments to Java 8, but it will slowly become accepted by the public over the next few months. There is no question that now is the time to make your code Java 8 compatible and migrate to Java 8 when Java 8 is secure and stable enough.

Refer to the article

Blog.csdn.net/shuaicihai/… Blog.csdn.net/qq\_3490816… www.jianshu.com/p/4df02599a… www.cnblogs.com/yangzhilong… www.cnblogs.com/JackpotHan/…