重学Java 8新特性 | 第6讲——方法引用与构造器引用

Posted 李阿昀

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了重学Java 8新特性 | 第6讲——方法引用与构造器引用相关的知识,希望对你有一定的参考价值。

我们为什么要使用方法引用与构造器引用呢?

如果不使用Lambda表达式进行程序编写的话,那么大可不必关注方法引用和构造器引用,但是如果使用Lambda表达式,再配合方法引用和构造器引用之后,那么就可以使Lambda编写匿名内部类的代码变得更加简洁。在不影响性能的前提下简洁的代码可以增强代码的可读性,当然是在阅读者知晓对方语法的前提下。

方法引用

什么是方法引用?

当要传递给Lambda体的操作,已经有实现的方法了,那么这时便可以使用方法引用了。或者,你也可以理解为方法引用是Lambda表达式的另外一种表现形式。

不过有一点需要我们注意,就是Lambda体中调用方法的参数列表与返回值类型,要与函数式接口中抽象方法的参数列表与返回值类型保持一致!

方法引用的语法格式

方法引用使用操作符::将方法名和对象或类的名字分隔开来。而且,方法引用主要有如下三种固定语法格式。

  • 第一种语法格式:对象::实例方法名。
  • 第二种语法格式:类::静态方法名。
  • 第三种语法格式:类::实例方法名。

接下来,我会为大家分别一一介绍这三种语法格式。

第一种语法格式:对象::实例方法名

观察如下Java程序,你会发现在Lambda体中有一个println方法已经完成了我们要操作的功能,即完成了在Lambda体中所写的功能。

package com.meimeixia.java8;

import org.junit.Test;

import java.io.PrintStream;
import java.util.function.Consumer;

/**
 *
 * @author liayun
 * @create 2021-12-11 16:00
 */
public class TestMethodRef 

    // 对象::实例方法名
    @Test
    public void test1() 
        // 未使用方法引用时
        // Consumer<String> con = (x) -> System.out.println(x);
        PrintStream ps1 = System.out;
        Consumer<String> con = (x) -> ps1.println(x);
    


那么在这种情况下我们就可以使用方法引用了!

package com.meimeixia.java8;

import org.junit.Test;

import java.io.PrintStream;
import java.util.function.Consumer;

/**
 *
 * @author liayun
 * @create 2021-12-11 16:00
 */
public class TestMethodRef 

    // 对象::实例方法名
    @Test
    public void test1() 
        // 未使用方法引用时
        // Consumer<String> con = (x) -> System.out.println(x);
        PrintStream ps1 = System.out;
        Consumer<String> con = (x) -> ps1.println(x);

        // 使用方法引用时
        PrintStream ps = System.out;
        Consumer<String> con1 = ps::println;
        // 以上还可以简写为下面这样,相信大家应该都看得懂
        Consumer<String> con2 = System.out::println;
        con2.accept("abcdef");
    


在使用方法引用时,有一个前提,大家得注意一下,就是需要实现的接口(例如Consumer<T>)中的抽象方法的参数列表与返回值类型要与我们当前调用的Lambda体中方法的参数列表与返回值类型保持一致!

再来看另外一个案例,使用Lambda表达式来实现供给型接口,如下所示。

@Test
public void test2() 
    // 未使用方法引用时
    Employee emp = new Employee();
    Supplier<String> sup = () -> emp.getName();
    String str = sup.get();
    System.out.println(str);

其中,Employee实体类的代码如下所示。

package com.meimeixia.java8;

/**
 * @author liayun
 * @create 2021-12-08 20:52
 */
public class Employee 

    private int id;
    private String name;
    private int age;
    private double salary;

    public Employee() 

    

    public Employee(int id, String name, int age, double salary) 
        this.id = id;
        this.name = name;
        this.age = age;
        this.salary = salary;
    

    public int getId() 
        return id;
    

    public void setId(int id) 
        this.id = id;
    

    public String getName() 
        return name;
    

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

    public int getAge() 
        return age;
    

    public void setAge(int age) 
        this.age = age;
    

    public double getSalary() 
        return salary;
    

    public void setSalary(double salary) 
        this.salary = salary;
    

    @Override
    public String toString() 
        return "Employee" +
                "id=" + id +
                ", name='" + name + '\\'' +
                ", age=" + age +
                ", salary=" + salary +
                '';
    


大家发现没有在以上Lambda体中是不是已经有实现的方法了啊,所以这时我们就可以使用方法引用了。

@Test
public void test2() 
    // 未使用方法引用时
    Employee emp = new Employee();
    Supplier<String> sup = () -> emp.getName();
    String str = sup.get();
    System.out.println(str);

    // 使用方法引用时
    Supplier<Integer> sup2 = emp::getAge;
    Integer num = sup2.get();
    System.out.println(num);

第二种语法格式:类::静态方法名

使用方法引用的这种格式,可以引用类的静态方法,就像下面这样。

// 类::静态方法名
@Test
public void test3() 
    // 未使用方法引用时
    Comparator<Integer> com = (x, y) -> Integer.compare(x, y);

    // 使用方法引用时
    Comparator<Integer> com1 = Integer::compare;

如果你怀疑上述使用方法引用时的写法,那么不妨去查看一下Integer类中compare方法的参数列表与返回值类型是否和Comparator接口中compare方法的参数列表与返回值类型保持一致。不用想,肯定是保持一致的,保持一致就可以像上面这样使用方法引用来书写代码了。

第三种语法格式:类::实例方法名

使用方法引用的这种格式得有一个前提,也可以说是规则,即若Lambda表达式参数列表中的第一个参数是实例方法的调用者,而第二个参数是实例方法的参数时,则可使用ClassName::method这种格式来书写代码。不知道,大家能记住嘛?

// 类::实例方法名
@Test
public void test4() 
    // 未使用方法引用时,来比较两个字符串是不是一样的。注意,BiPredicate接口是Predicate接口的子接口
    BiPredicate<String, String> bp = (x, y) -> x.equals(y);

    // 使用方法引用时
    BiPredicate<String, String> bp2 = String::equals;

构造器引用

构造器引用的语法格式是ClassName::new。它与函数式接口相结合,自动与函数式接口中方法兼容,可以把构造器引用赋值给定义的方法,只不过构造器的参数列表要与接口中抽象方法的参数列表保持一致!

为了演示构造器引用的使用,我们在Employee实体类中多写几个构造方法,如下所示。

package com.meimeixia.java8;

/**
 * @author liayun
 * @create 2021-12-08 20:52
 */
public class Employee 

    private int id;
    private String name;
    private int age;
    private double salary;

    public Employee() 

    

    public Employee(int id) 
        this.id = id;
    

    public Employee(int id, int age) 
        this.id = id;
        this.age = age;
    

    public Employee(int id, String name, int age, double salary) 
        this.id = id;
        this.name = name;
        this.age = age;
        this.salary = salary;
    

    public int getId() 
        return id;
    

    public void setId(int id) 
        this.id = id;
    

    public String getName() 
        return name;
    

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

    public int getAge() 
        return age;
    

    public void setAge(int age) 
        this.age = age;
    

    public double getSalary() 
        return salary;
    

    public void setSalary(double salary) 
        this.salary = salary;
    

    @Override
    public String toString() 
        return "Employee" +
                "id=" + id +
                ", name='" + name + '\\'' +
                ", age=" + age +
                ", salary=" + salary +
                '';
    


然后,我们就能使用构造器引用的方式来书写代码了。

// 构造器引用
@Test
public void test5() 
    // 未使用构造器引用时
    Supplier<Employee> sup = () -> new Employee();

    // 使用构造器引用时
    Supplier<Employee> sup2 = Employee::new;
    Employee emp = sup2.get();
    System.out.println(emp);

不知你有没有想过,Employee实体类中有好几个构造器,那么现在调用的是哪个构造器呢?道理其实跟方法引用一样,由于构造器的参数列表要与接口中抽象方法的参数列表保持一致,所以此时调用的必然就是无参的构造器,也就是说Java 8会根据函数式接口中抽象方法的参数列表自动匹配对应的构造器

如果此时我们想要调用Employee实体类中一个参数的构造器,那么该怎么办呢?是不是该像下面这样做啊!

@Test
public void test6() 
    Function<Integer, Employee> fun = (x) -> new Employee(x);

    Function<Integer, Employee> fun2 = Employee::new; // 注意,此时调用的是Employee实体类中一个参数的构造器
    Employee emp = fun2.apply(101);
    System.out.println(emp);

举一反三,大家试着思考一下,如果此时想要调用Employee实体类中两个参数的构造器,那么又该怎么办呢?很简单嘛,代码像下面这样写就行了。

@Test
public void test6() 
    Function<Integer, Employee> fun = (x) -> new Employee(x);

    Function<Integer, Employee> fun2 = Employee::new; // 注意,此时调用的是Employee实体类中一个参数的构造器
    Employee emp = fun2.apply(101);
    System.out.println(emp);

    // BiFunction接口是Function接口的子接口
    BiFunction<Integer, Integer, Employee> bf = Employee::new; // 注意,此时调用的是Employee实体类中两个参数的构造器

数组引用

数组引用的语法格式是Type[]::new

下面我会通过一个案例来简单使用一下数组引用。

// 数组引用
@Test
public void test7() 
    // 未使用数组引用时
    Function<Integer, String[]> fun = (x) -> new String[x];
    String[] strs = fun.apply(10);
    System.out.println(strs.length);

    // 使用数组引用时
    Function<Integer, String[]> fun2 = String[]::new;
    String[] strs2 = fun2.apply(20);
    System.out.println(strs2.length);

至此,Java 8中有关Lambda表达式的所有内容我就算是总结完了!接下来咱们就要开始学习Java 8中另外一个最核心的新特性(即Stream API)了。

以上是关于重学Java 8新特性 | 第6讲——方法引用与构造器引用的主要内容,如果未能解决你的问题,请参考以下文章

重学Java 8新特性 | 第5讲——函数式接口

重学Java 8新特性 | 第5讲——函数式接口

重学Java 8新特性 | 第2讲——Java 8新特性简介

重学Java 8新特性 | 第2讲——Java 8新特性简介

重学Java 8新特性 | 第2讲——Java 8新特性简介

重学Java 8新特性 | 第1讲——我们为什么要学习Java 8新特性?