day13 [Stream stream, method reference]

Keywords: Lambda Java Programming

day13 [Stream stream, method reference]

primary coverage

  • Stream flow
  • Method reference

Teaching objectives

  • Understanding the advantages of flow versus set
  • Understanding the Delayed Execution Characteristics of Flows
  • Streams can be captured through collections, maps, or arrays
  • Ability to master common flow operations
  • Ability to use output statements to reference 3
  • Can be referenced in four ways
  • Constructors that can use classes and arrays reference 8

Chapter 1 Stream flow

Speaking of Stream, I/O Stream is easy to think of. In fact, who stipulates that "flow" must be "IO flow"? In Java 8, thanks to the functional programming brought about by Lambda, a new Stream concept is introduced to solve the drawbacks of existing collection class libraries.

1.1 INTRODUCTION

Multi-step traversal code for traditional sets

Almost all collections, such as Collection or Map interfaces, support direct or indirect traversal operations. When we need to operate on the elements in the collection, besides the necessary addition, deletion and acquisition, the most typical is the collection traversal. For example:

import java.util.ArrayList;
import java.util.List;

public class Demo01ForEach {
    public static void main(String[] args) {
        List<String> list = new ArrayList<>();
        list.add("Zhang Wuji");
        list.add("Zhou Zhi Luo");
        list.add("Zhao Min");
        list.add("Zhang Qiang");
        list.add("Zhang Sanfeng");
        for (String name : list) {
          	System.out.println(name);
        }
    }  
}

This is a very simple set traversal operation: print out every string in the set.

The drawbacks of cyclic traversal

Java 8's Lambda allows us to focus more on What than How, which has previously been illustrated in conjunction with internal classes. Now let's take a closer look at the example code and find that:

  • The grammar of for loops is "how to do it"
  • What is the "for" loop?

Why use loops? Because it's traversal. But is cycling the only way to traverse? Traversal is a cycle in which each element is processed one by one, not sequentially from the first to the last. The former is the purpose, the latter is the way.

Imagine if you want to filter elements in a collection:

  1. Set A is filtered into subset B according to condition one.
  2. Then filter it into subset C according to condition two.

What can we do? Before Java 8, it might be:

import java.util.ArrayList;
import java.util.List;

public class Demo02NormalFilter {
  	public static void main(String[] args) {
      	List<String> list = new ArrayList<>();
        list.add("Zhang Wuji");
        list.add("Zhou Zhi Luo");
        list.add("Zhao Min");
        list.add("Zhang Qiang");
        list.add("Zhang Sanfeng");

        List<String> zhangList = new ArrayList<>();
        for (String name : list) {
            if (name.startsWith("Zhang")) {
              	zhangList.add(name);
            }
        }

        List<String> shortList = new ArrayList<>();
        for (String name : zhangList) {
            if (name.length() == 3) {
              	shortList.add(name);
            }
        }

        for (String name : shortList) {
          	System.out.println(name);
        }
    }
}

This code contains three loops, each of which functions differently:

  1. Firstly, all the people surnamed Zhang were screened.
  2. Then select the person whose name has three words.
  3. Finally, the results are printed out.

Whenever we need to operate on elements in a collection, we always need to loop, loop, and recycle. Is that taken for granted? * * No. Circulation is a way of doing things, not an end. On the other hand, using linear loops means traversing only once. If you want to traverse again, you can only use another loop to start from scratch.

So how can Stream, a derivative of Lambda, give us a more elegant way to write it?

Better Writing of Stream

Let's look at what elegance is with the Stream API of Java 8:

import java.util.ArrayList;
import java.util.List;

public class Demo03StreamFilter {
    public static void main(String[] args) {
        List<String> list = new ArrayList<>();
        list.add("Zhang Wuji");
        list.add("Zhou Zhi Luo");
        list.add("Zhao Min");
        list.add("Zhang Qiang");
        list.add("Zhang Sanfeng");

        list.stream()
          	.filter(s -> s.startsWith("Zhang"))
            .filter(s -> s.length() == 3)
            .forEach(System.out::println);
    }
}

Reading the literal meaning of the code directly can perfectly show the semantics of irrelevant logical ways: getting stream, filtering name sheet, filtering length of 3, printing one by one. The code does not reflect traversal using linear loops or any other algorithm. What we really need to do is better reflected in the code.

Summary of 1.2 Streaming Thought

Note: Please temporarily forget the inherent impression of traditional IO streams!

On the whole, the idea of flow is similar to the "production line" of factory workshop.

When multiple elements need to be manipulated (especially multi-step operations), considering performance and convenience, we should first put together a "model" step plan, and then implement it according to the plan.

This figure shows the filtering, mapping, skipping, counting and other multi-step operations, which is a set element processing scheme, and the scheme is a "functional model". Each box in the diagram is a "flow" that calls the specified method and can be converted from one flow model to another. The number 3 on the right is the final result.

Here filter, map, skip are all operating on the function model, and the set elements are not really processed. Only when the termination method count is executed will the entire model execute according to the specified policy. This is due to Lambda's delayed execution feature.

Note: "Stream stream" is actually a function model of a set element. It is not a set, nor a data structure. It does not store any elements (or their address values) in itself.

Stream is an element queue from a data source

  • Elements are specific types of objects that form a queue. Stream in Java does not store elements, but computes on demand.
  • Sources of data sources and streams. They can be collections, arrays, etc.

Unlike previous Collection operations, Stream operations have two basic features:

  • Pipelining: Intermediate operations return the flow object itself. This allows multiple operations to be connected in series to form a pipeline, like a fluent style. This can optimize operations such as laziness and short-circuiting.
  • Internal iteration: In the past, iterations on collections were performed explicitly outside collections by means of Iterator or enhanced for, which is called external iteration. Stream provides a way of internal iteration, and streams can call traversal methods directly.

When using a stream, it usually consists of three basic steps: obtaining a data source data conversion performing operations to obtain desired results, each time the original Stream object is transformed unchanged, and returning a new Stream object (which can be converted many times), which allows its operations to be arranged like a chain and become a pipeline.

1.3 Acquisition Stream

Java.util.stream.Stream<T> is the most commonly used stream interface newly added to Java 8. (This is not a functional interface.)

Getting a stream is very simple, there are several common ways:

  • All Collection collections can obtain streams by stream default method.
  • The static method of Stream interface can get the stream corresponding to the array.

Get the stream from Collection

First, the default stream method is added to the java.util.Collection interface to get the stream, so all of its implementation classes can get the stream.

import java.util.*;
import java.util.stream.Stream;

public class Demo04GetStream {
    public static void main(String[] args) {
        List<String> list = new ArrayList<>();
        // ...
        Stream<String> stream1 = list.stream();

        Set<String> set = new HashSet<>();
        // ...
        Stream<String> stream2 = set.stream();

        Vector<String> vector = new Vector<>();
        // ...
        Stream<String> stream3 = vector.stream();
    }
}

Getting streams from Map s

The java.util.Map interface is not a sub-interface of Collection, and its K-V data structure does not conform to the single characteristics of stream elements, so it needs to be divided into key, value or entry to obtain the corresponding stream.

import java.util.HashMap;
import java.util.Map;
import java.util.stream.Stream;

public class Demo05GetStream {
    public static void main(String[] args) {
        Map<String, String> map = new HashMap<>();
        // ...
        Stream<String> keyStream = map.keySet().stream();
        Stream<String> valueStream = map.values().stream();
        Stream<Map.Entry<String, String>> entryStream = map.entrySet().stream();
    }
}

Getting streams from arrays

If you use arrays instead of collections or maps, since it is impossible to add default methods to array objects, the Stream interface provides a static method of, which is simple to use:

import java.util.stream.Stream;

public class Demo06GetStream {
    public static void main(String[] args) {
        String[] array = { "Zhang Wuji", "Zhang Cu Shan", "Zhang Sanfeng", "Zhang Yiyuan" };
        Stream<String> stream = Stream.of(array);
    }
}

Note: The parameters of the of method are actually variable parameters, so arrays are supported.

1.4 Common Methods

The operation of flow model is very rich. Here we introduce some commonly used API s. These methods can be divided into two kinds:

  • Delay method: The return value type is still the Stream interface's own type of method, so chain invocation is supported. (Except the termination method, all the other methods are delay methods.)

  • Ending method: The return value type is no longer the Stream interface's own type of method, so chain calls like StringBuilder are no longer supported. In this section, termination methods include count and forEach methods.

Note: For more methods than this section, please refer to the API documentation yourself.

Processing one by one:forEvery

Although the method name is forEach, it is different from the "for-each" nickname in the for loop.

void forEach(Consumer<? super T> action);

This method receives a Consumer interface function and handles each stream element to the function for processing.

Review Consumer Interface

The java.util.function.Consumer<T> interface is a consumer interface.
The Consumer interface contains the abstract method void accept (T), which means consuming data of a specified generic type.

Basic use:

import java.util.stream.Stream;

public class Demo12StreamForEach {
    public static void main(String[] args) {
        Stream<String> stream = Stream.of("Zhang Wuji", "Zhang Sanfeng", "Zhou Zhi Luo");
        stream.forEach(name-> System.out.println(name));
    }
}

Filter:filter

One stream can be transformed into another subset through the filter method. Method signature:

Stream<T> filter(Predicate<? super T> predicate);

The interface receives a Predicate functional interface parameter (which can be a Lambda or method reference) as a filter condition.

Review the Predicate interface

We've learned about java.util.stream.Predicate functional interfaces before, and the only abstract method is:

boolean test(T t);

This method will produce a boolean value result, representing whether the specified condition is satisfied or not. If the result is true, then the filter method of Stream stream will retain the element; if the result is false, the filter method will discard the element.

Basic use

The filter method in Stream stream basically uses code such as:

import java.util.stream.Stream;

public class Demo07StreamFilter {
    public static void main(String[] args) {
        Stream<String> original = Stream.of("Zhang Wuji", "Zhang Sanfeng", "Zhou Zhi Luo");
        Stream<String> result = original.filter(s -> s.startsWith("Zhang"));
    }
}

Here, the condition for filtering is specified by Lambda expression: the surname must be Zhang.

Mapping: map

If you need to map elements in a stream to another stream, you can use the map method. Method signature:

<R> Stream<R> map(Function<? super T, ? extends R> mapper);

This interface requires a functional interface parameter that can convert T-type data in the current stream into another R-type stream.

Review Function Interface

We've learned about java.util.stream.Function functional interfaces before, and the only abstract method is:

R apply(T t);

This can convert a T type into a R type, and the action of this transformation is called "mapping".

Basic use

The map method in Stream stream basically uses code such as:

import java.util.stream.Stream;

public class Demo08StreamMap {
    public static void main(String[] args) {
        Stream<String> original = Stream.of("10", "12", "18");
        Stream<Integer> result = original.map(str->Integer.parseInt(str));
    }
}

In this code, the parameters of the map method are referenced by the method, converting the string type to int type (and automatically boxed as Integer class objects).

Number of statistics: count

As with the size method in the old Collection, streams provide count methods to count the number of elements:

long count();

This method returns a long value representing the number of elements (no longer an int value like the old set). Basic use:

import java.util.stream.Stream;

public class Demo09StreamCount {
    public static void main(String[] args) {
        Stream<String> original = Stream.of("Zhang Wuji", "Zhang Sanfeng", "Zhou Zhi Luo");
        Stream<String> result = original.filter(s -> s.startsWith("Zhang"));
        System.out.println(result.count()); // 2
    }
}

Take the first few: limit.

limit method can intercept convection, only the first n. Method signature:

Stream<T> limit(long maxSize);

The parameter is a long type. If the current length of the set is longer than the parameter, it will be intercepted; otherwise, it will not be operated on.Basic use:

import java.util.stream.Stream;

public class Demo10StreamLimit {
    public static void main(String[] args) {
        Stream<String> original = Stream.of("Zhang Wuji", "Zhang Sanfeng", "Zhou Zhi Luo");
        Stream<String> result = original.limit(2);
        System.out.println(result.count()); // 2
    }
}

Skip the first few: skip

If you want to skip the first few elements, you can use the skip method to get a new stream after interception:

Stream<T> skip(long n);

If the current length of the flow is greater than n, the first n will be skipped; otherwise, an air flow with a length of 0 will be obtained. Basic use:

import java.util.stream.Stream;

public class Demo11StreamSkip {
    public static void main(String[] args) {
        Stream<String> original = Stream.of("Zhang Wuji", "Zhang Sanfeng", "Zhou Zhi Luo");
        Stream<String> result = original.skip(2);
        System.out.println(result.count()); // 1
    }
}

Combination: concat

If you have two streams and want to merge them into one stream, you can use the static method concat of Stream interface:

static <T> Stream<T> concat(Stream<? extends T> a, Stream<? extends T> b)

Note: This is a static method, different from the concat method in java.lang.String.

The basic usage code of this method is as follows:

import java.util.stream.Stream;

public class Demo12StreamConcat {
    public static void main(String[] args) {
        Stream<String> streamA = Stream.of("Zhang Wuji");
        Stream<String> streamB = Stream.of("Zhang Cu Shan");
        Stream<String> result = Stream.concat(streamA, streamB);
    }
}

Exercise 1.5: Set Element Processing (Traditional Way)

subject

There are now two ArrayList collective storage teams with multiple members'names, requiring the use of traditional for loops (or enhanced for loops) to perform the following steps in turn:

  1. The first team needs only the name of a member whose name is three words; it is stored in a new collection.
  2. After the first team is screened, only the first three people are needed; they are stored in a new collection.
  3. The second team only needs the name of the member whose surname is Zhang; it is stored in a new collection.
  4. The second team does not need the first two people after screening; it is stored in a new collection.
  5. Combine two teams into one team; store them in a new set.
  6. Create Person objects by name; store them in a new collection.
  7. Print the entire team's Person object information.

The code for the two teams (assemblies) is as follows:

import java.util.ArrayList;
import java.util.List;

public class DemoArrayListNames {
    public static void main(String[] args) {
       //First team
        ArrayList<String> one = new ArrayList<>();

        one.add("Di Ali Gerba");
        one.add("Song Yuan Qiao");
        one.add("Su Xing He");
        one.add("Shi Tian Tian");
        one.add("Stone jade");
        one.add("Lao Tzu");
        one.add("Chuang-tzu");
        one.add("Master Hongqi");

        //Second team
        ArrayList<String> two = new ArrayList<>();
        two.add("Guli Nazha");
        two.add("Zhang Wuji");
        two.add("Zhao Liying");
        two.add("Zhang Sanfeng");
        two.add("Nicholas Zhao Si");
        two.add("Zhang Tian AI");
        two.add("Zhang Er dog");
		// ....
    }
}

The Person class code is:

public class Person {
    
    private String name;

    public Person() {}

    public Person(String name) {
        this.name = name;
    }

    @Override
    public String toString() {
        return "Person{name='" + name + "'}";
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }
}

Answer

Since the traditional for loop writing is used, then:

public class DemoArrayListNames {
    public static void main(String[] args) {
        List<String> one = new ArrayList<>();
        // ...

        List<String> two = new ArrayList<>();
        // ...

        // The first team only needs the name of a member whose name is three words.
        List<String> oneA = new ArrayList<>();
        for (String name : one) {
            if (name.length() == 3) {
                oneA.add(name);
            }
        }

        // After the first team is screened, only the first three are needed.
        List<String> oneB = new ArrayList<>();
        for (int i = 0; i < 3; i++) {
            oneB.add(oneA.get(i));
        }

        // The second team only needs the name of Zhang's members.
        List<String> twoA = new ArrayList<>();
        for (String name : two) {
            if (name.startsWith("Zhang")) {
                twoA.add(name);
            }
        }

        // The second team should not be the first two after screening.
        List<String> twoB = new ArrayList<>();
        for (int i = 2; i < twoA.size(); i++) {
            twoB.add(twoA.get(i));
        }

        // Combine the two teams into one team.
        List<String> totalNames = new ArrayList<>();
        totalNames.addAll(oneB);
        totalNames.addAll(twoB);

        // Create Person objects by name;
        List<Person> totalPersonList = new ArrayList<>();
        for (String name : totalNames) {
            totalPersonList.add(new Person(name));
        }

        // Print the entire team's Person object information.
        for (Person person : totalPersonList) {
            System.out.println(person);
        }
    }
}

The results are as follows:

Person{name='Song Yuan Qiao'}
Person{name='Su Xing He'}
Person{name='Shi Tian Tian'}
Person{name='Zhang Tian AI'}
Person{name='Zhang Er dog'}

Exercise 1.6: Stream Approach

subject

Replace the traditional for loop writing in the previous question with Stream streaming. The initial content of the two sets remains unchanged, and the definition of the Person class remains unchanged.

Answer

The equivalent Stream processing code is:

import java.util.ArrayList;
import java.util.List;
import java.util.stream.Stream;

public class DemoStreamNames {
    public static void main(String[] args) {
        List<String> one = new ArrayList<>();
        // ...

        List<String> two = new ArrayList<>();
        // ...

        // The first team only needs the name of a member whose name is three words.
        // After the first team is screened, only the first three people are needed.
        Stream<String> streamOne = one.stream().filter(s -> s.length() == 3).limit(3);

        // The second team only needs the name of Zhang's members.
        // The second team should not be the first two after screening.
        Stream<String> streamTwo = two.stream().filter(s -> s.startsWith("Zhang")).skip(2);

        // Combine the two teams into one team.
        // Create Person objects by name;
        // Print the entire team's Person object information.
        Stream.concat(streamOne, streamTwo).map(Person::new).forEach(System.out::println);
    }
}

The operation effect is exactly the same:

Person{name='Song Yuan Qiao'}
Person{name='Su Xing He'}
Person{name='Shi Tian Tian'}
Person{name='Zhang Tian AI'}
Person{name='Zhang Er dog'}

Chapter II Methodological Citation

When we use Lambda expressions, the code we actually pass in is a solution: what parameters to do with. So consider a situation: if we already have the same solution in place for the operation scheme specified in Lambda, is it necessary to write duplicate logic again?

2.1 Redundant Ambda Scenarios

Let's look at a simple functional interface to apply Lambda expressions:

@FunctionalInterface
public interface Printable {
  	void print(String str);
}

The only abstract method in the Printable interface, print, receives a string parameter for printing and displaying it. The code to use it through Lambda is simple:

public class Demo01PrintSimple {
    private static void printString(Printable data) {
      	data.print("Hello, World!");
    }

    public static void main(String[] args) {
      	printString(s -> System.out.println(s));
    }
}

The printString method only calls the print method of the Printable interface, regardless of where the string will be printed by the implementation logic of the print method. The main method specifies the specific operation scheme of the functional interface Printable through Lambda expression: after obtaining String (type can be deduced, so omitted) data, output it in the console.

2.2 Problem Analysis

The problem with this code is that the println(String) method in the System.out object has already been implemented to print out the string on the console. Since what Lambda wants to do is call the println(String) method, why call it manually?

2.3 Using Method Reference to Improve Code

Can you omit Lambda's grammatical format (although it's already quite concise)? Just "quote" the past.

public class Demo02PrintRef {
    private static void printString(Printable data) {
        data.print("Hello, World!");
    }

    public static void main(String[] args) {
      	printString(System.out::println);
    }
}

Note the double colon: Writing, which is called "method reference", and double colon is a new grammar.

2.4 Method Reference

Double colon: A reference operator whose expression is called a method reference. If the function scheme Lambda is expressing already exists in the implementation of a method, it can be referred to as a substitute for Lambda by double colons.

semantic analysis

For example, in the previous example, there is an overloaded println(String) method in the System.out object that happens to be what we need. Then for the printString method's functional interface parameters, compared with the following two methods, they are completely equivalent:

  • Lambda expression: s - > System. out. println (s);
  • Method Reference: System.out::println

The first kind of semantics refers to: after getting the parameters, it is handled by Lambda and then passed to System.out.println method.

The semantics of the second kind of equivalent writing refers to the direct substitution of the println method in System.out for Lambda. The execution effect of the two methods is exactly the same, while the second method uses the existing scheme and is more concise.

Note: The parameters passed in Lambda must be the type that the method in the method reference can receive, otherwise an exception will be thrown.

Derivation and Ellipsis

If Lambda is used, there is no need to specify parameter types and no need to specify overload forms according to the principle of "deductibility is omitted" - they will all be deduced automatically. If the method is used, it can also be deduced according to the context.

Functional interfaces are the basis of Lambda, and method references are the twin brothers of Lambda.

The following code will call different overload forms of the println method, changing the functional interface to an int-type parameter:

@FunctionalInterface
public interface PrintableInteger {
  	void print(int str);
}

Since the unique matching overload can be automatically deduced when the context changes, there is no change in the method reference:

public class Demo03PrintOverload {
    private static void printInteger(PrintableInteger data) {
      	data.print(1024);
    }

    public static void main(String[] args) {
      	printInteger(System.out::println);
    }
}

This method reference will automatically match to the overloaded form of println(int).

2.5 Reference to member methods by object names

This is the most common use, as in the previous example. If a member method already exists in a class:

public class MethodRefObject {
    public void printUpperCase(String str) {
      	System.out.println(str.toUpperCase());
    }
}

Functional interfaces are still defined as:

@FunctionalInterface
public interface Printable {
  	void print(String str);
}

Then when you need to use this printUpperCase member method to replace Lambda of Printable interface, you already have an object instance of the MethodRefObject class. You can refer to the member method by the object name. The code is:

public class Demo04MethodRef {
    private static void printString(Printable lambda) {
      	lambda.print("Hello");
    }

    public static void main(String[] args) {
        MethodRefObject obj = new MethodRefObject();
        printString(obj::printUpperCase);
    }
}

2.6 Referencing static methods by class name

Because static method abs already exists in the java.lang.Math class, there are two ways of writing when we need to call this method through Lambda. First is the functional interface:

@FunctionalInterface
public interface Calcable {
  	int calc(int num);
}

The first is to use Lambda expressions:

public class Demo05Lambda {
    private static void method(int num, Calcable lambda) {
      	System.out.println(lambda.calc(num));
    }

    public static void main(String[] args) {
      	method(-10, n -> Math.abs(n));
    }
}

But the better way to use method references is:

public class Demo06MethodRef {
    private static void method(int num, Calcable lambda) {
      	System.out.println(lambda.calc(num));
    }

    public static void main(String[] args) {
      	method(-10, Math::abs);
    }
}

In this example, the following two expressions are equivalent:

  • Lambda expression: n - > Math. ABS (n)
  • Method reference: Math::abs

2.7 Reference to member methods through super

If inheritance exists, method references can also be used to replace super calls in Lambda. First, functional interfaces:

@FunctionalInterface
public interface Greetable {
  	void greet();
}

Then comes the content of the parent Human:

public class Human {
    public void sayHello() {
      	System.out.println("Hello!");
    }
}

Finally, the content of the subclass Man, which uses Lambda's notation:

public class Man extends Human {
    @Override
    public void sayHello() {
        System.out.println("Hello everyone,I am Man!");
    }

    //Define method, parameter passing Greetable interface
    public void method(Greetable g){
        g.greet();
    }

    public void show(){
        //Call the method method method, using the Lambda expression
        method(()->{
            //Create a Human object and call the sayHello method
            new Human().sayHello();
        });
        //Simplified Lambda
        method(()->new Human().sayHello());
        //Replacing parent objects with super keywords
        method(()->super.sayHello());
    }
}

But it would be better to use method references to call the sayHello method in the parent class, such as another subclass Woman:

public class Man extends Human {
    @Override
    public void sayHello() {
        System.out.println("Hello everyone,I am Man!");
    }

    //Define method, parameter passing Greetable interface
    public void method(Greetable g){
        g.greet();
    }

    public void show(){
        method(super::sayHello);
    }
}

In this example, the following two expressions are equivalent:

  • Lambda expression: () - > super. sayHello ()
  • Method reference: super::sayHello

2.8 Reference to member methods through this

This represents the current object. If the method you need to refer to is the member method in the current class, you can use the method reference in the format "this:: member method". The first is a simple functional interface:

@FunctionalInterface
public interface Richable {
   	void buy();
}

The following is a Husband class for husbands:

public class Husband {
    private void marry(Richable lambda) {
      	lambda.buy();
    }

    public void beHappy() {
      	marry(() -> System.out.println("Buy a house"));
    }
}

The happy method beHappy calls the marriage method marry, whose parameter is the functional interface Richable, so a Lambda expression is needed. But if the content of this Lambda expression already exists in this class, Husband Husband Husband Husband Husband Husband Husband Husband Husband Husband Husband Husband Husband Husband Husband Husband Husband Husband Husband Husb

public class Husband {
    private void buyHouse() {
      	System.out.println("Buy a house");
    }

    private void marry(Richable lambda) {
      	lambda.buy();
    }

    public void beHappy() {
      	marry(() -> this.buyHouse());
    }
}

If you want to cancel the Lambda expression and replace it with method references, it is better to write as follows:

public class Husband {
    private void buyHouse() {
      	System.out.println("Buy a house");
    }

    private void marry(Richable lambda) {
      	lambda.buy();
    }

    public void beHappy() {
      	marry(this::buyHouse);
    }
}

In this example, the following two expressions are equivalent:

  • Lambda expression: () - > this. buyHouse ()
  • Method Reference: this::buyHouse

Constructor reference for class 2.9

Because the name of the constructor is exactly the same as the class name, it is not fixed. So the constructor reference uses the format representation of the class name:: new. The first is a simple Person class:

public class Person {
    private String name;

    public Person(String name) {
        this.name = name;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }
}

Then it is a functional interface for creating Person objects:

public interface PersonBuilder {
    Person buildPerson(String name);
}

To use this functional interface, you can use the Lambda expression:

public class Demo09Lambda {
    public static void printName(String name, PersonBuilder builder) {
      	System.out.println(builder.buildPerson(name).getName());
    }

    public static void main(String[] args) {
      	printName("Zhao Liying", name -> new Person(name));
    }
}

But with constructor references, there is a better way to write:

public class Demo10ConstructorRef {
    public static void printName(String name, PersonBuilder builder) {
      	System.out.println(builder.buildPerson(name).getName());
    }

    public static void main(String[] args) {
      	printName("Zhao Liying", Person::new);
    }
}

In this example, the following two expressions are equivalent:

  • Lambda expression: name - > New Person (name)
  • Method reference: Person::new

2.10 Array Constructor Reference

Arrays are also subclasses of Object s, so they also have constructors, with slightly different syntax. A functional interface is required if it corresponds to the use scenario of Lambda:

@FunctionalInterface
public interface ArrayBuilder {
  	int[] buildArray(int length);
}

When the interface is applied, it can be expressed by Lambda:

public class Demo11ArrayInitRef {   
    private static int[] initArray(int length, ArrayBuilder builder) {
      	return builder.buildArray(length);
    }

    public static void main(String[] args) {
      	int[] array = initArray(10, length -> new int[length]);
    }
}

But a better way to write it is to use an array constructor reference:

public class Demo12ArrayInitRef {
    private static int[] initArray(int length, ArrayBuilder builder) {
      	return builder.buildArray(length);
    }

    public static void main(String[] args) {
      	int[] array = initArray(10, int[]::new);
    }
}

In this example, the following two expressions are equivalent:

  • Lambda expression: length - > New Int [length]
  • Method reference: int[]::new

Posted by danwguy on Thu, 16 May 2019 03:20:36 -0700