A, Lambda

1.1 format

The JDK has supported Lambda expressions since version 1.8, which allow you to pass a function as an argument to a method. Prior to JDK 1.8, we could only do this with anonymous expressions. However, anonymous expressions are cumbersome and have a lot of template code, which makes it difficult to parameterize behavior. Lamdba can solve this problem well. The basic syntax for Lambda expressions is as follows:

(parameters) -> expression
Copy the code

Or use curly braces:

(parameters) -> { statements; }
Copy the code

Lambda expressions have the following characteristics:

  • ** Optional parameters: ** There is no need to declare the parameter type, the compiler will automatically infer based on the context;
  • ** Optional argument parentheses: ** The enclosing argument parentheses can be omitted if and only if there is only one argument;
  • ** Optional curly braces: ** If the body has only one expression, curly braces are not required;
  • ** If the body has only one expression, the value of that expression is the return value of the entire Lambda expression, and there is no need to explicitly return the return keyword.

1.2 Behavior parameterization

As we said above, Lambda expressions mainly solve the problem of behavior parameterization. What is behavior parameterization? Here is a concrete example:

/** * defines a functional interface *@param<T> Parameter type */
@FunctionalInterface
public interface CustomPredicate<T> {
    boolean test(T t);
}
Copy the code
/** * set filter *@paramList Sets to be filtered *@paramPredicate function interface *@param<T> The type of the element in the collection *@returnThe set of elements that satisfy the condition */
public static <T> List<T> filter(List<T> list, CustomPredicate<T> predicate) {
    ArrayList<T> result = new ArrayList<>();
    for (T t : list) {
        // Add the elements that meet the criteria to the return collection
        if (predicate.test(t)) result.add(t);
    }
    return result;
}

Copy the code

For different types of collections, we can express different filtering behaviors by passing different Lambda expressions as parameters, which is called behavior parameterization:

List<Integer> integers = Arrays.asList(1.2.3.4.5);
filter(integers, x -> x % 2= =0);  // Filter out all even numbers

List<Employee> employees = Arrays.asList(
    new Employee("Zhang".21.true),
    new Employee("Li mou".30.true),
    new Employee("Wang mou".45.false));
filter(employees, employee -> employee.getAge() > 25); // Filter out all employees older than 25
Copy the code

Note that when we declare the interface above, we use the @functionalinterface annotation, which indicates that the current interface is a FunctionalInterface. A functional interface is one that contains only one abstract method; That is, no matter how many default and static methods an interface has, as long as it has only one abstract method, it is a functional interface. With the @functionalInterface modifier, the compiler alerts you when the interface has more than one abstract method.

Wherever functional interfaces are used, Lambda expressions can be used as shorthand. For example, the Runnable interface is a functional interface, which we can abbreviate using Lambda expressions:

new Thread(() -> {
    System.out.println("hello");
});
Copy the code

1.3 Method references and constructor references

Following the example above, if we wanted to filter out all regular employees, we could write it as follows:

filter(employees, employee -> employee.isOfficial());
Copy the code

You can also use the shorthand form of a method reference:

filter(employees, Employee::isOfficial);
Copy the code

In addition to method references, constructors can be referenced as shown in the following example:

Stream<Integer> stream = Stream.of(1.3.5.2.4);
stream.collect(Collectors.toCollection(ArrayList::new));  ToCollection (()->new ArrayList<>())
Copy the code

Both method references and constructor references are intended to make code more concise.

2. Functional interfaces

There are a number of built-in functional interfaces in the JDK, which can meet the requirements of most scenarios. The four basic ones are as follows:

1. Consumer

: Consumer input variable, no return value:

@FunctionalInterface
public interface Consumer<T> {
    void accept(T t); . }Copy the code

2. Consumer

:

@FunctionalInterface
public interface Supplier<T> {
    T get(a);
}
Copy the code

Function

: Function

: Function

:
,>
,>
,>

@FunctionalInterface
public interface Function<T.R> {
    R apply(T t); . }Copy the code

Predicate

: Determines whether certain conditions are met for variables of type T, returns true if they are, and flase otherwise:

@FunctionalInterface
public interface Predicate<T> {
    boolean test(T t); . }Copy the code

Other functional interfaces are extensions and extensions of these four basic types. The BiFunction and BinaryOperator interfaces are used as examples:

  • BiFunction

    : is an extension of Function

    , which accepts only one input parameter. BiFunction can be used to receive two different types of input parameters;
    ,>
    ,>
  • BinaryOperator

    : is a special case of BiFunction where both input parameters and return values are of the same type, usually used for binary operations. The definition is as follows:
@FunctionalInterface
public interface BiFunction<T.U.R> {
    R apply(T t, U u);
}

@FunctionalInterface
public interface BinaryOperator<T> extends BiFunction<T.T.T> {... }Copy the code

Here’s how to use the BinaryOperator:

/** * Perform reduction */
public static <T> T reduce(List<T> list, T initValue, BinaryOperator<T> binaryOperator) {
    for (T t : list) {
        initValue = binaryOperator.apply(initValue, t);
    }
    return initValue;
}

public static void main(String[] args) {
    List<Integer> integers = Arrays.asList(1.2.3.4.5);
    reduce(integers, 0, (a, b) -> a + b); // Sum output: 15
    reduce(integers, 1, (a, b) -> a * b); // Quadrature output: 120
}
Copy the code

Create a flow

Another big improvement in JDK 1.8 is the introduction of streams, which make it possible to efficiently process data through streams, Lamda expressions, and functional interfaces. There are four common ways to create a flow:

1. The value is created

Create with the specified value using the static method stream.of () :

Stream<String> stream = Stream.of("a"."b "."c"."d");
Copy the code

2. Created by a collection or array

Arrays.stream() is created from the specified array using the static method:

String[] strings={"a"."b "."c"."d"};
Stream<String> stream = Arrays.stream(strings);
Copy the code

Call the stream() method of the collection class to create:

List<String> strings = Arrays.asList("a", "b ", "c", "d");
Stream<String> stream = strings.stream();
Copy the code

The stream() method is defined in the Collection interface and is the default method, so most collections can use it to create streams:

public interface Collection<E> extends 可迭代<E> {
    default Stream<E> stream(a) {
        return StreamSupport.stream(spliterator(), false); }}Copy the code

3. Create a file

try (Stream<String> lines = Files.lines(Paths.get("pom.xml"), StandardCharsets.UTF_8)) {
    lines.forEach(System.out::println);
} catch (IOException e) {
    e.printStackTrace();
}
Copy the code

4. Created by a function

In addition to the above methods, you can also create an infinite Stream with the stream.iterate () and stream.generate () methods:

  • Stream.iterate() takes two arguments: the first is the initial value; The second argument is a functional interface with the same input and output values, mainly used to iteratively produce new elements, as shown in the following example:

    // Outputs 0 through 9
    Stream.iterate(0, x -> x + 1).limit(10).forEach(System.out::print);
    Copy the code
  • Stream.generate() takes a supply function as an argument to generate a new element from that function:

    // Outputs random numbers in turn
    Stream.generate(Math::random).limit(10).forEach(System.out::print);
    Copy the code

4. Operation flow

4.1 Basic Operations

Once a Stream is created, various methods on the Stream class can be used to process the data in the Stream. Common methods are as follows:

operation role The return type Type/functional interface used
filter Filter the elements that match the criteria Stream<T> Predicate<T>
distinct Filtering repeating elements Stream<T>
skip Skips a specified number of elements Stream<T> long
limit Limit the number of elements Stream<T> long
map Perform specific transformation operations on elements Stream<T> Function<T,R>
flatMap Flattening an element performs a specific transformation operation Stream<T> Function<T,Stream<R>>
sorted Sort the elements Stream<T> Comparator<T>
anyMatch Whether any element exists that satisfies the specified condition boolean Predicate<T>
noneMatch Whether all elements do not satisfy the specified condition boolean Predicate<T>
allMatch Whether all elements satisfy the specified condition boolean Predicate<T>
findAny Returns any element that meets the specified criteria Optional<T>
findFirst Returns the first element that meets the specified condition Optional<T>
forEach Perform specific operations on all elements void Cosumer<T>
collect Using the collector R Collector<T, A, R>
reduce Perform the reduction operation Optional<T> BinaryOperator<T>
count Count the number of elements in the stream long

Note: The operations in the table above that return type Stream<T> are intermediate operations, meaning that you can continue to call other methods to process the Stream. Any other operation with a return type is a termination operation, indicating that the process has stopped.

The following is an example:

Stream.iterate(0, x -> x + 1)       / / build flow
    .limit(20)                        // Limit the number of elements
    .skip(10)                        // Skip the first 10 elements
    .filter(x -> x % 2= =0)         // Filter out all even numbers
    .map(x -> "Even." + x)            // Perform conversion operations on elements
    .forEach(System.out::println);    // Prints out all elements
Copy the code

The following output is displayed:

Even :10 Even :12 Even :14 Even :16 Even :18Copy the code

The flatMap() method in the above table takes a parameter that is a functional interface Function
> mapper extends R>> mapper extends R>> mapper extends R>> mapper extends R>> mapper extends R>> mapper extends R>> mapper extends R>> mapper extends R>> mapper extends R>> mapper extends R>> Mapper extends R>> Mapper

String[] strings = {"hello"."world"};

Arrays.stream(strings)
    .map(x -> x.split(""))              / / get split: [' h ', 'e', 'l', 'l', 'o'], [' w ', 'o', 'r', 'l', 'd']
    .flatMap(x -> Arrays.stream(x))  / / will be flatter each array processing is: 'h', 'e', 'l', 'l', 'o', 'w', 'o', 'r', 'l', 'd'
    .forEach(System.out::println);
Copy the code

The reduce() method in the table above takes two arguments: the first argument represents the initial value to perform the reduction operation; The second argument is the functional interface we described above, BinaryOperator

, as shown in the following example:

Stream.iterate(0, x -> x + 1).limit(10)
    .reduce(0, (a, b) -> a + b); // Perform the sum operation
Copy the code

4.2 numerical flow

The above code effectively sums all the elements in the Stream, so we can also call sum() as a shorthand method, but note that the element types in the Stream are wrapper types:

Stream<Integer> stream = Stream.iterate(0, x -> x + 1); // The package type is Integer
Copy the code

The sum() method is defined on an IntStream, and we need to convert the stream to a specific numeric stream using mapToInt() :

Stream.iterate(0, x -> x + 1).limit(10).mapToInt(x -> x).sum();
Copy the code

Similar methods include mapToLong() and mapToDouble(). Call boxed() if you want to convert a numeric stream to the original stream, which is equivalent to boxing its elements:

IntStream intStream = Stream.iterate(0, x -> x + 1).limit(10).mapToInt(x -> x);
Stream<Integer> boxed = intStream.boxed();
Copy the code

Five, collector

The most powerful termination operation in Stream is collect(), which takes a Collector Collector as an argument and can collect elements from the Stream into a collection, or group, partition, and so on. Java has multiple built-in Collectors, which can be invoked using static methods of the Collectors class. The common Collectors are as follows:

The factory method The return type Used for
toList List<T> Collect all elements of the stream into a List
toSet Set<T> Collect all elements of the stream into a Set
toCollection Collection<T> Collects all elements of the stream into the specified collection
counting Long Calculates the number of all elements in the stream
summingInt Integer Converts all elements in the stream to integers and calculates their sum
averagingInt Double Convert all elements in the stream to integers and calculate their average
summarizingInt IntSummaryStatistics Converts all elements in the stream to integers and returns statistics, including maximum, minimum,

Information such as sum and average
joining String Converts all elements in the stream to a string and concatenates them using the given concatenator
maxBy Optional<T> Find the Optional of the largest element in the flow
minBy Optional<T> Find the Optional of the smallest element in the stream
reducing The type generated by the specification operation Performs a reduction operation on all elements in the flow
collectingAndThen Converts the returned type Collect all elements of the stream into the specified collection before performing specific operations on the collection
groupingBy Map<K,List<T>> Group all elements in the flow
partitionBy Map<Boolean,List<T>> Partitioning is performed on all elements in the flow

The following is an example:

Stream<Integer> stream = Stream.of(1.2.3.4.4.5.6); 

stream.collect(Collectors.toSet());  // [1, 2, 3, 4, 5, 6]
stream.collect(Collectors.toList()); // [1, 2, 3, 4, 4, 5, 6]
stream.collect(Collectors.toCollection(ArrayList::new)); // [1, 2, 3, 4, 4, 5, 6]
stream.collect(Collectors.counting()); // 7 is equivalent to stream.count();
stream.collect(Collectors.summarizingInt(x -> x)); // IntSummaryStatistics{count=7, sum=25, min=1, average=3.571429, Max =6}
stream.collect(Collectors.maxBy((Integer::compareTo))); // Optional[6]
stream.collect(Collectors.reducing(1, (a, b) -> a * b)); Stream.reduce (1, (a, b) -> a * b);
collect(Collectors.collectingAndThen(Collectors.toSet(), Set::size)); // Collect all elements in the Set, then calculate the size of the Set
Copy the code

Note: Each of the above terminations can only be demonstrated separately, because only one termination can be performed on a stream. And after the termination of the operation, you can’t get to make any operation, the flow will be thrown. Java lang. An IllegalStateException: the stream has already had been operated upon the or closed.

5.2 grouping

A grouping collector can implement functionality similar to the database groupBy clause. Assume the following employee information exists:

Stream<Employee> stream = Stream.of(new Employee("Zhang"."Male"."A company".20),
    new Employee("Li mou"."Female"."A company".30),
    new Employee("Wang mou"."Male"."Company B".40),
    new Employee("Tian Mou"."Female"."Company B".50));
Copy the code
public class Employee {
    
    private String name;
    private String gender;
    private String company;
    private int age;
    
    @Override
    public String toString(a) {return "Employee{" + "name='" + name + '\' ' + '} '; }}Copy the code

If you need to group by company at this point, you can use the groupingBy() collector:

stream.collect(Collectors.groupingBy(Employee::getCompany)); The group result is as follows: {B company =[Employee{name='王某'}, Employee{name='Tian Mou'}], A company =[Employee{name='zhang'}, Employee{name='li mou'}}]Copy the code

If you want to count the number of people per company after grouping, you can also pass a Collector Collector as its second argument to groupingBy() and call its overloaded method:

stream.collect(Collectors.groupingBy(Employee::getCompany, Collectors.counting())); The corresponding result is as follows: {company B =2, company A =2
}    
Copy the code

Since the second argument is a Collector, this means you can pass in a grouping Collector to do multi-level grouping as shown in the following example:

stream.collect(Collectors.groupingBy(Employee::getCompany, Collectors.groupingBy(Employee::getGender))); Group by company and then by gender as follows: {B company ={female =[Employee{name='Tian Mou'[the Employee {}], male = name ='王某'}]}, A company ={female =[Employee{name='li mou'[the Employee {}], male = name ='zhang'}}}]Copy the code

Alternatively, grouping conditions can be customized in blocks of code, as shown in the following example:

Map<String, List<Employee>> collect = stream.collect(Collectors.groupingBy(employee -> {
    if (employee.getAge() <= 30) {
        return "Young Employee";
    } else if (employee.getAge() < 50) {
        return "Middle-aged employee";
    } else {
        return "Older workers"; }})); The corresponding group results are as follows: {middle-aged Employee =[Employee{name='王某'}], young Employee =[Employee{name='zhang'}, Employee{name='li mou'}], old Employee =[Employee{name='Tian Mou'}}]Copy the code

5.3 partition

Partitioning is a special case of grouping, that is, the elements that meet the specified conditions are divided into one group, and the elements that do not meet the specified conditions are divided into another group. The two are basically similar in use, as shown in the following example:

stream.collect(Collectors.partitioningBy(x -> "A company".equals(x.getCompany()))); The partition result is as follows: {false=[Employee{name='王某'}, Employee{name='Tian Mou'}].true=[Employee{name='zhang'}, Employee{name='li mou'}}]Copy the code

Parallel flow

Converting an ordinary Stream to a parallel Stream is as simple as calling Stream’s parallel() method:

stream.parallel();
Copy the code

At this point, all elements in the stream are evenly distributed across multiple threads for processing. The internal use of a parallel stream is the ForkJoinPool thread pool, whose default number of threads is the number of processors. This value can be viewed by runtime.getruntime ().availableProcessors() and usually does not need to be changed.

Is also currently there is no way for a specific stream specifies the number of threads, only by changing the system property java.util.concurrent.ForkJoinPool.com mon. Parallelism value to change all the parallel flow using the number of threads, the sample is as follows:

System.setProperty("java.util.concurrent.ForkJoinPool.common.parallelism"."12"); 
Copy the code

If you want to change a parallel Stream back to a normal serial Stream, you simply call the sequential() method of Stream:

stream.sequential();
Copy the code

The resources

Raoul-gabriel Urma/Mario Fusco/Alan Mycroft. Java 8 In Action. Posts and Telecommunications Press. 2016-04-01

For more articles, please visit the full stack Engineer manual at GitHub.Github.com/heibaiying/…