Java8 new feature stream stream

New features of JDK1.8

1 Introduction

JDK1.8 has been released for a long time and is already in use in many enterprises. And Spring5 and SpringBoot2.0 are recommended to use JDK1.8 or above. So we must keep pace with the times and embrace change.

This version of Jdk8 contains more than ten new features in languages, compilers, libraries, tools and JVM. In this article we will learn new features in:

  • Lambda expressions
  • functional interface
  • method reference]
  • Interface default methods and static methods
  • Optional
  • Streams
  • parallel array

2. Lambda expressions

functional programming

Lambda expressions, also known as closures, are the most important new feature driving the release of Java 8. Lambda allows functions to be passed as parameters of a method (functions are passed as parameters into methods). It can make the code more concise and compact.

2.1 Basic syntax:

(参数列表) -> {代码块}

requires attention:

  • The parameter type can be omitted, and the compiler can infer it by itself
  • If there is only one parameter, the parentheses can be omitted
  • If the code block is only one line of code, the braces can also be omitted
  • If the code block is one line and is an expression with results, returnit can be omitted

Note: In fact, think of Lambda expressions as a shorthand for anonymous inner classes. Of course, the premise is that the anonymous inner class must correspond to an interface, and there must be only one function in the interface! Lambda expression is to write functions directly: parameter list, code body, return value and other information,用函数来代替完整的匿名内部类 !

2.2 Usage example

Example 1: Multiple parameters

Prepare a collection:

// 准备一个集合
List<Integer> list = Arrays.asList(10, 5, 25, -15, 20);

Suppose we want to sort the collection, let's first look at the writing method of JDK7, we need to construct one through anonymous inner class Comparator:

// Jdk1.7写法
Collections.sort(list,new Comparator<Integer>() {
    
    
    @Override
    public int compare(Integer o1, Integer o2) {
    
    
        return o1 - o2;
    }
});
System.out.println(list);// [-15, 5, 10, 20, 25]

If it is jdk8, we can use the new collection API: sort(Comparator c)method to receive a comparator, and we use Lambda to replace Comparatorthe anonymous inner class:

// Jdk1.8写法,参数列表的数据类型可省略:
list.sort((i1,i2) -> {
    
     return i1 - i2;});

System.out.println(list);// [-15, 5, 10, 20, 25]

Comparing the method Comparatorin compare(), you will find that the Lambda expression written here is just compare()the shorthand form of the method, and JDK8 will compile it into an anonymous inner class. Isn't it much simpler!

Don't worry, we found that the code block here has only one line of code, which conforms to the previous omission rules. We can abbreviate it as:

// Jdk8写法
// 因为代码块是一个有返回值的表达式,可以省略大括号以及return
list.sort((i1,i2) -> i1 - i2);
Example 2: Single parameter

Taking the collection just now as an example, now we want to traverse the elements in the collection and print them.

First use the jdk1.7 method:

// JDK1.7遍历并打印集合
for (Integer i : list) {
    
    
    System.out.println(i);
}

jdk1.8 adds a method to the collection: foreach(), which receives a function that operates on elements:

// JDK1.8遍历并打印集合,因为只有一个参数,所以我们可以省略小括号:
list.forEach(i -> System.out.println(i));
Example 3: Assign Lambda to a variable

The essence of Lambda expression is actually an anonymous inner class, so we can actually assign a Lambda expression to a variable.

// 将一个Lambda表达式赋值给某个接口:
Runnable task = () -> {
    
    
    // 这里其实是Runnable接口的匿名内部类,我们在编写run方法。
    System.out.println("hello lambda!");
};
new Thread(task).start();

However, the above usage is rare, and Lambda is generally used as a parameter directly.

Example 4: Implicit final

The essence of the Lambda expression is actually an anonymous inner class, and when an anonymous inner class accesses an external local variable, the variable must be declared as final! However, we do not need to declare when using Lambda expressions final. This does not mean that we violate the rules of anonymous inner classes, because the bottom layer of Lambda will implicitly set the variable to final, and the variable must not be modified in subsequent operations:

Correct example:

// 定义一个局部变量
int num = -1;
Runnable r = () -> {
    
    
    // 在Lambda表达式中使用局部变量num,num会被隐式声明为final
    System.out.println(num);
};
new Thread(r).start();// -1

Error case:

// 定义一个局部变量
int num = -1;
Runnable r = () -> {
    
    
    // 在Lambda表达式中使用局部变量num,num会被隐式声明为final,不能进行任何修改操作
    System.out.println(num++);
};
new Thread(r).start();//报错

3. Functional interface

After the previous study, I believe that everyone has a preliminary understanding of Lambda expressions. in conclusion:

  • Lambda expressions are shorthand for anonymous inner classes of interfaces
  • The interface must satisfy: there is only one function inside

RunnableIn fact, such an interface is called a functional interface, and what we have learned Comparatoris a typical representative of a functional interface. But in practice, the functional interface is very fragile. As long as someone adds one more method to the interface, then the interface is not a functional interface, and the compilation will fail. Java 8 provides a special annotation @FunctionalInterfaceto overcome the above-mentioned vulnerability and explicitly indicate the functional interface. @FunctionalInterfaceMoreover, in the jdk8 version, annotations have been added to many existing interfaces , such as Runnablethe interface:

[External link picture transfer failed, the source site may have an anti-leeching mechanism, it is recommended to save the picture and upload it directly (img-ODT4QdNs-1669729296892)(runnable.png)]

In addition, Jdk8 provides some functional interfaces by default for us to use:

3.1 Function type interface

@FunctionalInterface
public interface Function<T, R> {
    
    
	// 接收一个参数T,返回一个结果R
    R apply(T t);
}

Function represents a function that has parameters and returns a value. There are many similar Function interfaces:

interface name describe
BiFunction<T,U,R> A function that takes two arguments of type T and U and returns a result of type R
DoubleFunction<R> A function that accepts a double type parameter and returns an R type result
IntFunction<R> A function that accepts an int type parameter and returns an R type result
LongFunction<R> A function that accepts a long type parameter and returns an R type result
ToDoubleFunction<T> Receive T type parameters and return double type results
ToIntFunction<T> Receive T type parameter and return int type result
ToLongFunction<T> Receive T type parameters and return long type results
DoubleToIntFunction Receive double type parameter and return int type result
DoubleToLongFunction Receive double type parameter and return long type result

Do you see the pattern? These are a class of functional interfaces, derived from Function, which either specify that the parameters do not determine the return result, or specify that the result does not know the parameter type, or both.

3.2 Consumer Series

@FunctionalInterface
public interface Consumer<T> {
    
    
	// 接收T类型参数,不返回结果
    void accept(T t);
}

Like the Function series, the Consumer series has various derivative interfaces, which are not listed here. However, they all have similar characteristics: that is, they do not return any results.

3.3 Predicate series

@FunctionalInterface
public interface Predicate<T> {
    
    
	// 接收T类型参数,返回boolean类型结果
    boolean test(T t);
}

The parameters of the Predicate series are not fixed, but the return must be boolean type.

3.4 Supplier series

@FunctionalInterface
public interface Supplier<T> {
    
    
	// 无需参数,返回一个T类型结果
    T get();
}

Supplier series, the English translation is "supplier", as the name suggests: only output, no collection. Therefore, it does not accept any parameters and returns a result of type T.

4. Method reference

Method references allow developers to pass existing methods as variables. Method references can be used with Lambda expressions.

4.1 Syntax:

There are four types of method references:

grammar describe
classname::staticmethodname A reference to a static method of a class
classname::non-static methodname A reference to a non-static method of a class
instance object::non-static method name A non-static method reference to the specified instance object of the class
classname::new class constructor reference

4.2 Examples

First, we write a collection tool class and provide a method:

    public class CollectionUtil{
    
    
        /**
         * 利用function将list集合中的每一个元素转换后形成新的集合返回
         * @param list 要转换的源集合
         * @param function 转换元素的方式
         * @param <T> 源集合的元素类型
         * @param <R> 转换后的元素类型
         * @return
         */
        public static <T,R> List<R> convert(List<T> list, Function<T,R> function){
    
    
            List<R> result = new ArrayList<>();
            list.forEach(t -> result.add(function.apply(t)));
            return result;
        }
    }

You can see that this method receives two parameters:

  • List<T> list: the collection that needs to be transformed
  • Function<T,R>: Function interface, receiving type T and returning type R. Use this function interface to convert the element T in the list into R type

Next, let's look at specific cases:

4.2.1 Static method references of classes

List<Integer> list = Arrays.asList(1000, 2000, 3000);

We need to convert the elements in this collection to hexadecimal, and we need to call Integer.toHexString()the method:

public static String toHexString(int i) {
    
    
    return toUnsignedString0(i, 4);
}

This method accepts an i type and returns a Stringtype that can be used to construct a Functionfunctional interface:

Let's first follow the original writing method of Lambda, the incoming Lambda expression will be compiled into an interface, and the elements of the original collection are converted Functionin the interface :Integer.toHexString(i)

// 通过Lambda表达式实现
List<String> hexList = CollectionUtil.convert(list, i -> Integer.toHexString(i));
System.out.println(hexList);// [3e8, 7d0, bb8]

In the above Lambda expression code block, there is only Integer.toHexString()a reference to the method, and there is no other code, so we can directly pass the method as a parameter, and the compiler will handle it for us. This is a static method reference:

// 类的静态方法引用
List<String> hexList = CollectionUtil.convert(list, Integer::toHexString;
System.out.println(hexList);// [3e8, 7d0, bb8]

4.2.2 Non-static method references of classes

Next, we capitalize the elements in Stringthe collection we just generated hexList, using the toUpperCase() method of the String class:

public String toUpperCase() {
    
    
    return toUpperCase(Locale.getDefault());
}

This time it is a non-static method, which cannot be called by a class name, but an instance object, so there are some differences from the previous implementation. We receive each string in the collection s. But unlike the above then snot toUpperCase()the parameter, but the caller:

// 通过Lambda表达式,接收String数据,调用toUpperCase()
List<String> upperList = CollectionUtil.convert(hexList, s -> s.toUpperCase());
System.out.println(upperList);// [3E8, 7D0, BB8]

Because the code body only has toUpperCase()the right call, you can pass the method as a parameter reference, and you can still abbreviate:

// 类的成员方法
List<String> upperList = CollectionUtil.convert(hexList, String::toUpperCase);
System.out.println(upperList);// [3E8, 7D0, BB8]

4.2.3 Non-static method references specifying instances

The following requirement is as follows. We first define a number Integer num = 2000, then use this number to compare with each number in the collection, and put the comparison result into a new collection. To compare objects, we can use Integerthe compareTomethod:

public int compareTo(Integer anotherInteger) {
    
    
    return compare(this.value, anotherInteger.value);
}

First use Lambda to implement,

List<Integer> list = Arrays.asList(1000, 2000, 3000);

// 某个对象的成员方法
Integer num = 2000;
List<Integer> compareList = CollectionUtil.convert(list, i -> num.compareTo(i));
System.out.println(compareList);// [1, 0, -1]

num.compareTo(i)Similar to the previous ones, there are still only correct calls in the Lambda code block here , so it can be abbreviated. However, it should be noted that the caller of the method is not an element of the collection, but an external local variable num, so it cannot be used Integer::compareTo, because the caller of the method cannot be determined in this way. To specify the caller, you need to use 对象::方法名:

// 某个对象的成员方法
Integer num = 2000;
List<Integer> compareList = CollectionUtil.convert(list, num::compareTo);
System.out.println(compareList);// [1, 0, -1]

4.2.4 Constructor references

The last scene: use the numbers in the collection as the millisecond value, construct Datethe object and put it into the collection, here we need to use the constructor of Date:

/**
  * @param   date   the milliseconds since January 1, 1970, 00:00:00 GMT.
  * @see     java.lang.System#currentTimeMillis()
  */
public Date(long date) {
    
    
    fastTime = date;
}

We can receive each element in the collection, and then pass the element as Datea constructor parameter:

// 将数值类型集合,转为Date类型
List<Date> dateList = CollectionUtil.convert(list, i -> new Date(i));
// 这里遍历元素后需要打印,因此直接把println作为方法引用传递了
dateList.forEach(System.out::println);

In the above Lambda expression implementation, the code body has only new Date()one line of code, so it can also be abbreviated by method reference. But the problem is, the constructor has no name, we can only use newkeywords instead:

// 构造方法
List<Date> dateList = CollectionUtil.convert(list, Date::new);
dateList.forEach(System.out::println);

Note two points:

  • System.out::println in the above code is actually a reference to the non-static method println of the specified object System.out
  • If there are multiple constructors, it may not be possible to distinguish and cause the transfer to fail

5. Default methods and static methods of interfaces

Java 8 expands the meaning of an interface with two new concepts: default methods and static methods.

5.1 Default method

The default method allows developers to add new methods to existing interfaces without breaking binary compatibility, that is, it does not force those classes that implement the interface to also implement the newly added method.

The difference between default methods and abstract methods is that abstract methods require an implementation whereas default methods do not. The default method provided by the interface will be inherited or overridden by the implementation class of the interface. The example code is as follows:

private interface Defaulable {
    
    
    // Interfaces now allow default methods, the implementer may or 
    // may not implement (override) them.
    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 Defaulable interface defines a default method notRequired() using the keyword default. The DefaultableImpl class implements this interface and inherits the default method in this interface by default; the OverridableImpl class also implements this interface, but overrides the default method of the interface and provides a different implementation.

5.2 Static methods

Another interesting feature brought by Java 8 is that static methods can be defined in the interface, and we can directly call these static methods with the interface. The example code is as follows:

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

The following code snippet combines default method and static method usage scenarios:

public static void main( String[] args ) {
    
    
    // 调用接口的静态方法,并且传递DefaultableImpl的构造函数引用来构建对象
    Defaulable defaulable = DefaulableFactory.create( DefaultableImpl::new );
    System.out.println( defaulable.notRequired() );
	// 调用接口的静态方法,并且传递OverridableImpl的构造函数引用来构建对象
    defaulable = DefaulableFactory.create( OverridableImpl::new );
    System.out.println( defaulable.notRequired() );
}

The output of this code is as follows:

Default implementation
Overridden implementation

Since the implementation of the default method on the JVM provides support at the bytecode level, it is very efficient. Default methods allow interfaces to be improved without breaking existing inheritance hierarchies. The application of this feature in the official library is: java.util.Collectionadding new methods to the interface, such as stream(), parallelStream(), forEach()and removeIf()and so on.

Although the default method has so many benefits, it should be used with caution in actual development: in a complex inheritance system, the default method may cause ambiguity and compilation errors. If you want to know more details, you can refer to the official documentation.

6. Optional

The most common bug in Java applications is null exceptions.

OptionalJust a container that can store values ​​of type T or null. It provides some useful interfaces to avoid explicit nullchecks, you can refer to the official Java 8 documentation for more details.

Let's look at an example of using Optional: a value that may be empty or a value of a certain type:

Optional< String > fullName = Optional.ofNullable( null );
System.out.println( "Full Name is set? " + fullName.isPresent() );        
System.out.println( "Full Name: " + fullName.orElseGet( () -> "[none]" ) ); 
System.out.println( fullName.map( s -> "Hey " + s + "!" ).orElse( "Hey Stranger!" ) );

If Optionalthe instance holds a non-null value, isPresent()the method returns true, otherwise it returns false; if Optionalthe instance holds null, orElseGet()the method can accept a default value generated by a lambda expression; map()the method can convert the value of the existing Optionalinstance into a new value; orElse()the method is the same as orElseGet()The method is similar, but returns the default value passed in when null is held, instead of being generated by Lambda.

The output of the above code is as follows:

Full Name is set? false
Full Name: [none]
Hey Stranger!

Let's look at another simple 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();

The output of this example is:

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

For more details, please refer to the official documentation.

7. Streams

The new Stream API (java.util.stream) brings production-environment functional programming to the Java library. This is by far the largest improvement to the Java library so that developers can write more efficient, concise and compact code.

The Steam API greatly simplifies collection operations (we will see more than just collections later), first look at this class called Task:

public class Streams  {
    
    
    private enum Status {
    
    
        OPEN, CLOSED
    };

    private static final class Task {
    
    
        private final Status status;
        private final Integer points;

        Task( final Status status, final Integer points ) {
    
    
            this.status = status;
            this.points = points;
        }

        public Integer getPoints() {
    
    
            return points;
        }

        public Status getStatus() {
    
    
            return status;
        }

        @Override
        public String toString() {
    
    
            return String.format( "[%s, %d]", status, points );
        }
    }
}

The Task class has a points property, and there are two states: OPEN or CLOSED. Now suppose there is a collection of tasks:

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

First look at a question: How many OPEN states are there in this task set? Calculate their points attribute sum. Before Java 8, to solve this problem, you need to use foreach loop to traverse the task collection; but in Java 8, you can use steams to solve it: it includes a list of a series of elements, and supports sequential and parallel processing.

// Calculate total points of all active tasks using sum()
final long totalPointsOfOpenTasks = tasks
    .stream()
    .filter( task -> task.getStatus() == Status.OPEN )
    .mapToInt( Task::getPoints )
    .sum();

System.out.println( "Total points: " + totalPointsOfOpenTasks );

The console output from running this method is:

Total points: 18

There are many knowledge points worth mentioning here. First, tasksthe collection is converted into a representation; second steam, operations on the filter out all of them ; third, operations convert streams into collections based on the methods of each instance in the collection ; finally, sums are computed by methods, resulting in the final result.steamfilterCLOSEDtaskmapToInttaskstaskTask::getPointstaskIntegersum

Before learning the next example, you need to remember some knowledge points of steams (click here for more details). Operations on Steam can be divided into intermediate operations and late operations.

The intermediate operation will return a new steam - performing an intermediate operation (such as filter) does not perform the actual filtering operation, but creates a new steam, and puts the eligible elements in the original steam into the newly created steam .

Late operations (such as forEach or sum) will traverse the steam and get the result or incidental results; after the execution of the late operation, the steam processing line has been processed and cannot be used. In almost all cases, late operations traverse steam immediately.

Another value of steam is the creative support for parallel processing (parallel processing). For the above set of tasks, we can use the following code to calculate the sum of the points of all tasks:

// 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 );

Here we use the parallel method to process all tasks in parallel, and use the reduce method to calculate the final result. The console output is as follows:

Total points(all tasks): 26.0

For a collection, it is often necessary to group elements in it according to certain conditions. Using the API provided by steam can quickly complete such tasks, the code is as follows:

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

The console output is as follows:

{
    
    CLOSED=[[CLOSED, 8]], OPEN=[[OPEN, 5], [OPEN, 13]]}

The last example question about the tasks set is: how to calculate the proportion of the points of each task in the set in the set, the specific processing code is as follows:

// 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 );

The console output is as follows:

[19%, 50%, 30%]

Finally, as mentioned before, the Steam API can not only act on Java collections, traditional IO operations (reading data line by line from a file or network) can benefit from steam processing, here is a small example:

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 );
}

The Stream's methods onClose()return an equivalent Stream with the additional handle that close()will be executed when the Stream's method is called. The Stream API, Lambda expressions, and method references supported by interface default methods and static methods are Java 8's response to the modern paradigm of software development.

8. Parallel Arrays

The Java 8 version has added many new methods to support parallel array processing. Most importantly parallelSort(), it can significantly speed up array sorting on multi-core machines. The following examples demonstrate the methods of the parallelXxx family:

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

The above codes use the parallelSetAll() method to generate 20,000 random numbers, and then use the parallelSort() method to sort them. This program outputs the first 10 elements of both the unordered and sorted arrays. The output of the above example code is:

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

Guess you like

Origin blog.csdn.net/qq_31686241/article/details/128105924