Understand Java method references (method references: "double colon:")

Keywords: Lambda Java

Method Reference

When using Lambda expressions, the code we actually pass in is a solution: what parameters to do with.Consider a case where, if the same scenario already exists for the scenario we specified in Lambda, is it necessary to rewrite the duplicate logic?

Redundant Lambda Scenes

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

@FunctionalInterface
public interface Printable {
    /**
     * Receives a string parameter and prints it
     * @param str Character string
     */
    public abstract void print(String str);
}

 

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

public class Demo01PrintSimple {

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

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

}

The printString method calls the print method of the Printable interface, regardless of where the print method's implementation logic prints the string.The main method uses a Lambda expression to specify how the Printable function interface operates by outputting String data in the console after the type is deduced and therefore omitted.

 

problem analysis

The problem with this code is that the println(String) method in the System.out object is an obvious implementation of how to print out a string to the console.Since all Lambda wants to do is call the println(String) method, why do you call it manually yourself?

 

Reference improvement code by method

public class Demo02PrintRef {

    public static void main(String[] args) {
        printString(System.out::println);
    }
    
    private static void printString(Printable data) {
        data.print("Hello, World!");
    }

}

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

 

Method References

Double colon:: is a reference operator and the expression it is in is called a method reference.If the functional schema Lambda is expressing already exists in the implementation of a method, it can be referenced as an alternative to Lambda by double colons.

 

semantic analysis

For example, in the System.out object, there is an overloaded println(String) method that is exactly what we need.Then, for the function interface parameters of the printString method, the two writing methods are equivalent:

// Lambda Expression Writing
s -> System.out.println(s);
// Method Reference Writing
System.out::println
  • The first semantics refers to taking the parameter and passing it to the System.out.println method for processing by Lambda.
  • The semantics of the second equivalent writing refers to having the println method in System.out directly replace Lambda.The execution of the two methods is exactly the same, while the second method refers to a writing method that is more concise and reuses the existing scheme.

Note: The parameters passed in Lambda must be of the type that the method in the method reference can receive, otherwise compilation errors (errors, not exceptions) will occur.

As in the example above:

(int s) -> System.out.println(s)

The errors are as follows:

Error:(x, y) java: Incompatible type: Incompatible parameter type in lambda expression

x,y are locations of errors and can be interpreted as point coordinates in a Cartesian coordinate system.

 

Derivation and Omitting

If Lambda is used, then according to the principle of "deductible is omitted", there is no need to specify parameter types or specified overload forms - they will all be automatically deduced.If you use method references, you can also derive from the context.

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

 

The following code calls different overloads of the println method, changing the functional interface to an int-type parameter:

@FunctionalInterface
public interface PrintableInteger {
    /**
     * Receive an int type parameter and print it
     * @param i int Type parameters
     */
    public abstract void print(int i);
}

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

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

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

}

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

 

Referencing member methods by object name

This is the most common use, as in the above 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 {
    /**
     * Receives a string parameter and prints it
     * @param str Character string
     */
    public abstract void print(String str);
}

Once you get the parameters, Lambda handles them and passes them to the toUpperCase() method:

public class Demo04MethodRef1 {

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

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

}

Since the printUpperCase() method has been defined above, it implements the function of passing the incoming string to the toUpperCase() method for processing and then printing out.

At this point, when you need to use this printUpperCase member method to replace Lambda of the Printable interface, you already have an object instance of the MethodRefObject class, and you can reference the member method by the object name, code:

public class Demo04MethodRef2 {
    
    public static void main(String[] args) {
        MethodRefObject obj = new MethodRefObject();
        printString(obj::printUpperCase);
    }
    
    private static void printString(Printable lambda) {
        lambda.print("Hello");
    }
    
}

Run the program with the same output in both ways:

HELLO

 

Referencing static methods by class name

Since the static method abs already exists in the java.lang.Math class, there are two writings when we need to call it through Lambda.First is the functional interface:

@FunctionalInterface
public interface CalculationAble {
    /**
     * The interface passes in an int-type parameter, which is customized to return an int-type data
     */
    int calculation(int num);
}

The first is to use Lambda expressions:

public class Demo5Lambda {

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

    private static void method(int num, CalculationAble lambda) {
        System.out.println(lambda.calculation(num));
    }
    
}

But a better way to write a reference to a method is:

public class Demo5MethodRef {

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

    private static void method(int num, CalculationAble reference) {
        System.out.println(reference.calculation(num));
    }

}

In this example, the following two writings are equivalent:

// Lambda Expression
n -> Math.abs(n)
// Method Reference
Math::abs

 

Referencing member methods through super

If there is an inheritance relationship, a method reference can also be used instead when a super call is required in Lambda.First is the functional interface:

@FunctionalInterface
public interface GreetAble {
    /**
     * greetings
     */
    void greet();
}

Then there is the content of the parent Human:

public class Human {

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

}

Finally, there is the content of the subclass Man, which uses the notation Lambda:

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

    /**
     * Define method, parameter passing GreetAble interface
     * @param g The Lambda expression passed in here
     */
    public void method(GreetAble g) {
        g.greet();
    }

    /**
     * Call method, use Lambda expression
     */
    public void show(){

        // Establish Human Object, call sayHello Method
        method(() -> { new Human().sayHello(); });
        
        // Simplify Lambda
        method(() -> new Human().sayHello());

        // Use super Keyword instead of parent object
        method(() -> super.sayHello());
        
    }
    
}

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

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

    /**
     * Define method, parameter passing GreetAble interface
     * @param g Reference Method
     */
    public void method(GreetAble g) {
        g.greet();
    }

    /**
     * Call method, use reference method
     */
    public void show(){
        method(super::sayHello);
    }

}

In this example, the following two writings are equivalent:

// Lambda Expression
() -> super.sayHello()

// Method Reference
super::sayHello

 

Referencing member methods through this

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

@FunctionalInterface
public interface RichAble {
    /**
     * Shopping
     */
    void buy();
}

Here's a husband of the Husband class:

public class Husband01 {
    /**
     * marry
     * @param lambda Functional interfaces, shopping
     */
    private void marry(RichAble lambda) {
        lambda.buy();
    }

    /**
     * Be happy
     */
    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 required.But if the content of this Lambda expression already exists in this class, Husband's husband class can be modified:

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

    /**
     * marry
     * @param lambda Functional interfaces, shopping
     */
    private void marry(RichAble lambda) {
        lambda.buy();
    }

    /**
     * Be happy
     */
    public void beHappy() {
        marry(() -> this.buyHouse());
    }
}

If you want to cancel the Lambda expression and replace it with a method reference, a better way to write it is:

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

    /**
     * marry
     * @param lambda Functional interfaces, shopping
     */
    private void marry(RichAble lambda) {
        lambda.buy();
    }

    /**
     * Be happy
     */
    public void beHappy() {
        marry(this::buyHouse);
    }
}

In this example, the following two writings are equivalent:

// Lambda Expression
() -> this.buyHouse()

// Method Reference
this::buyHouse

 

Constructor reference for class

Since 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 this.name;
    }

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

}

Then there is the functional interface used to create the Person object:

@FunctionalInterface
public interface PersonBuilder {
    /**
     * Create Person Object
     * @param name Person Object Name
     * @return Person object
     */
    Person buildPerson(String name);
}

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

public class Demo06Lambda {

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

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

}

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

public class Demo07ConstructorRef {

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

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

}

In this example, the following two writings are equivalent:

// Lambda Expression
name -> new Person(name)

// Method Reference Person::new

 

 

Arrays are also subclasses of Object s, so they also have constructors, with slightly different syntax.If a functional interface is required for the use scenario of Lambda:

@FunctionalInterface
public interface ArrayBuilder {
    /**
     * Create Functional Interface for Arrays
     * @param length Array length
     * @return Array storing int type
     */
    int[] buildArray(int length);
}

When applying this interface, you can use the Lambda expression:

public class Demo08ArrayInitRef {

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

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

}

But a better way to write it is to use the constructor references of arrays:

public class Demo09ArrayInitRef {

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

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

}

 

In this example, the following two writings are equivalent:

// Lambda Expression
length -> new int[length]

// Method Reference
int[]::new

Posted by bp90210 on Sat, 11 Apr 2020 17:59:21 -0700