浅析Java中的方法引用

2008年的今天,科比领取了属于他的MVP奖杯,又是想念老大的一天┭┮﹏┭┮

人年纪大了,一个有关的字眼都会激起心中那思念的层层涟漪~


浅析Java中的方法引用_第1张图片
source



1. 引入

在具体理解Java中的方法引用之前,我们首先来回顾一下之前已经所学的一些东西,并由由这些东西来看一下方法引用出现的意义是什么,以及方法引用在程序中是如何使用的。

假设我们现在定义了一个函数式接口(为了方便和后面的内容连接,这里以函数式接口为例)如下所示,它其中只有一个抽象方法,实现输出所接收的字符串参数:

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

那么如何使用定义的接口呢?到目前来说,函数式接口的使用三种有三种方式:

  • 创建接口的实现类,通过调用实现类的对象使用

    public class PrintableImpl implements Printable{
           
        @Override
        public void print(String s) {
           
            System.out.println(s);
        }
    }
    
  • 通过匿名内部类直接创建对象使用

    public class PrintDemo {
           
        public static void main(String[] args) {
           
            String str = "hello world...";
            new Printable() {
           
                @Override
                public void print(String s) {
           
                    System.out.println(s);
                }
            }.print(str);
        }
    }
    
  • 通过Lambda表达式使用

    public class PrintDemo {
           
        public static void main(String[] args) {
           
            String str = "hello world...";
            show(str, s-> System.out.println(s));
        }
    
        public static void show(String str, Printable p){
           
            p.print(str);
        }
    }
    

Lambda表达式的使用已经在很大程度上简化了代码的书写,并且很好的体现了函数式编程的思想。Lambda表达式传递的代码就是一种实现逻辑:拿什么参数做什么操作,如上面的案例中只有将传入的s输出,并没有其他的处理逻辑。这样看起来好像已经很好了,代码相当的简洁。如果中间的实现逻辑较复杂,而且我们需要在程度的不同地方多次使用相同的处理逻辑,那么就要多次重复的写相同处理逻辑的Lambda表达式,不免显得冗余。

方法引用的出现可以有效的解决上述问题,当对象和方法都已经存在时,可以直接使用方法引用来优化Lambda表达式。如上所示的Lambda表达式:

show(str, s-> System.out.println(s));

System.out对象和println()已经存在,因此按照方法引用的思想,Lambda表达式可写为:

show(str, System.out::println);

2. 概念

show(str, System.out::println);为例说明,::称为引用运算符,而它所在的表达式就被称为方法引用。如果Lambda表达式要表达的函数方案已经存在于某个方法的实现中,可以直接使用::来引用该方法代替Lambda表达式。就上面对于同一种操作的两种实现方法来看:

  • Lambda表达式:拿到参数后将其通过Lambda表达式传给System.out.println()进行输出
  • 方法引用:直接使用System.out中的println()来代替Lambda表达式,它的写法更加简洁,体现了复用的思想

3. 使用方法

Java8之后方法引用有四种使用方式:

使用方式 方法引用写法 Lambda表达式写法
静态方法引用 ClassName::staticMethod (args) -> ClassName.staticMethod(args)
类的实例方法引用 object::instanceMethod (args) -> object.instanceMethodName(args)
对象的实例方法引用 ClassName::instanceMethod (args) -> ClassName.instanceMethod(args)
构造方法引用 ClassName::new (args) -> new ClassName(args)

3.1 通过对象名引用成员方法

我们继续使用如下的函数式接口:

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

并创建StringConvert类来实现字符串的大小写转换:

public class StringConvert {
     
    public void StringToUppercase(String s){
     
        System.out.println(s.toUpperCase());
    }

    public static void StringToLowercase(String s){
     
        System.out.println(s.toLowerCase());
    }
}

我们实现一个方法实现将一个字符串传递给Printable接口,然后调用其中的print()

 public static void showString(String str, Printable p){
     
        p.print(str);
    }

那么如何来使用StringConvert类中的成员方法呢?同样我们可以使用Lambda表达式进行使用:

public class StringConvertDemo {
     
    public static void main(String[] args) {
     
        String str = "HELLO world...";
        // 通过Lambda表达式使用成员方法
        showString(str, (s)->{
     
            StringConvert st = new StringConvert();
            st.StringToUppercase(s);  // HELLO WORLD...
        });

    }
    public static void showString(String str, Printable p){
     
        p.print(str);
    }
}

根据方法引用使用的两大前提

  • 对象名已存在
  • 成员方法已存在

可知,同样可以使用方法引用。对象名我们使用匿名对象,方法名就是StringToUppercase。因此,使用方法引用可写作:

public class StringConvertDemo {
     
    public static void main(String[] args) {
     
        String str = "HELLO world...";

        // 通过对象名引用成员方法
        showString(str, new StringConvert()::StringToUppercase);  // HELLO WORLD...
        System.out.println("------------");
    }
    
    public static void showString(String str, Printable p){
     
        p.print(str);
    }
}

通过new StringConvert()::StringToUppercase来实现接口的实现逻辑,而且在不同的地方调用都可以使用它,而不必重写Lambda中的逻辑。

3.2 通过类名直接引用静态方法

如上所示,在StringConvert类中我们还定义了一个静态方法StringToLowercase()来实现字符串转为小写形式。有了方法引用,我们就可以直接通过类名引用类中的静态方法:

public class StringConvertDemo {
     
    public static void main(String[] args) {
     
        String str = "HELLO world...";

        // 通过类名直接引用静态方法
        showString(str, StringConvert::StringToLowercase);  // hello world...
    }
    public static void showString(String str, Printable p){
     
        p.print(str);
    }
}

3.3 通过super关键字应用父类的方法

首先创建一个Person类作为父类,类中只有一个成员方法,用于接受一个字符串然后输出一句话

public class Person {
     
    public void say(String s){
     
        System.out.println("Say hello to " + s);
    }
}

然后定义一个子类Student,类中重写了父类中的say()。同时创建成员方法showInfo()来继续使用前面的printable接口,用来输出传入的字符串:

package MethodReference;

public class Student extends Person{
     
    private String name;
	private int age;
	
    public Student() {
     
    }

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

    public Student(String name, int age) {
     
        this.name = name;
        this.age = age;
    }

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

    @Override
    public void say(String s) {
     
        System.out.println("Show love to " + s);
    }
    
    public void showInfo(String s, Printable p){
     
        p.print(s);
    }
}

那么如何使用父类中的say()呢?最直接的方法就是直接创建父类对象,通过调用对象的say()实现:

public void method(String s){
     
    showInfo(s, (str)->{
     
        Person p = new Person();
        p.say(str);  // Say hello to kobe
    });
}

通过前面所学的对于super关键字的使用可知,我们可以在子类中可以使用super.methodName的方法来直接显式使用父类中方法。因此,上面的代码可写作:

public void method(String s){
     
    showInfo(s, str-> super.say(str));  // Say hello to kobe
}

浅理解Java中的继承

如果使用方法引用,代码可继续简化写作:

public void method(String s){
     
    showInfo(s, super::say);  // // Say hello to kobe
}

3.4 通过this关键字使用本类成员方法

同样是使用上面的Person类和Student类,如何使用本类中的成员方法呢?一种是通过类名直接引用,另一种是使用this.methodName显式引用

public void method1(String s){
     
    showInfo(s, str->say(str));  //Show love to kobe
    showInfo(s, str->this.say(str));  // Show love to kobe
}

而如果使用方法引用可直接写作:

public void method1(String s){
     
    showInfo(s, this::say);  // Show love to kobe
}

3.5 类的构造器引用

上面的Student类中定义了一个无参构造和两个带参构造,那么如何使用方法引用来调用构造方法创建对象呢?首先新建一个函数式接口,接收字符串返回Person对象

@FunctionalInterface
public interface Studentbuilder {
     
    Student building(String s);
}

在Student类中新建方法showInfo1(),方法体中通过Studentbuilder接口创建Student对象,然后输出对象的名字。

public void showInfo1(String s, Studentbuilder p){
     
    Student stu = p.building(s);
    System.out.println(stu.getName());
}

使用new 关键字可以在Lambda表达式中直接创建对象:

public void method2(String s){
     
    showInfo1(s, str-> new Student(str));  // kobe
}

而使用方法引用后,上面的代码可写作:

public void method2(String s){
     
	showInfo1(s, Student::new);  // kobe
}

完整Student类实现和测试代码:

public class Student extends Person{
     
    private String name;
	private int age;
	 
    public Student() {
     
    }

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

    public Student(String name, int age) {
     
        this.name = name;
        this.age = age;
    }

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

    @Override
    public void say(String s) {
     
        System.out.println("Show love to " + s);
    }

    public void showInfo(String s, Printable p){
     
        p.print(s);
    }

    public void showInfo1(String s, Studentbuilder p){
     
        Student stu = p.building(s);
        System.out.println(stu.getName());
    }
	
	// 通过super关键字应用父类的方法
    public void method(String s){
     
        showInfo(s, (str)->{
     
            Person p = new Person();
            p.say(str);  // Say hello to kobe
        });
        System.out.println("------------");

        showInfo(s, str-> super.say(str));  // Say hello to kobe
        System.out.println("------------");

        showInfo(s, super::say);  // // Say hello to kobe
        System.out.println("------------");
    }
	
	// 通过this关键字使用本类成员方法
    public void method1(String s){
     
        showInfo(s, str->say(str));  //Show love to kobe
        System.out.println("------------");

        showInfo(s, str->this.say(str));  // Show love to kobe
        System.out.println("------------");

        showInfo(s, this::say);  // Show love to kobe
        System.out.println("------------");
    }


    // 使用类名引用new创建对象
    public void method2(String s){
     
        showInfo1(s, str-> new Student(str));  // kobe
        System.out.println("------------");

        showInfo1(s, Student::new);  // kobe
    }

    public static void main(String[] args) {
     
        new Student().method("kobe");

        new Student().method1("kobe");

        new Student().method2("kobe");
    }
}

3.6 数组的构造器引用

首先创建一个函数式接口Arraybuilder,接口中的抽象方法实现根据传递的size创建int型数组。

@FunctionalInterface
public interface Arraybuilder {
     
    int[] building(int size);
}

如果使用Lambda表达式,代码可写作:

public class ArrayDemo {
     
    public static void main(String[] args) {
     
        int[] arr = build(5, (len) -> new int[len]);
        System.out.println(arr.length);  // 5
    }

    public static int[] build(int size, Arraybuilder ab){
     
       return  ab.building(size);
    }
}

而使用方法引用可简化写作:

public class ArrayDemo {
     
    public static void main(String[] args) {
     
        int[] arr2 = build(10, int[]::new);
        System.out.println(arr2.length);  // 10
    }

    public static int[] build(int size, Arraybuilder ab){
     
       return  ab.building(size);
    }
}

其他类型的数组创建同理。

不管是通过方法引用简化什么样的Lambda表达式,只有当Lambda表达式中只调用一个方法,方法可能是对象的实例方法、类的静态方法、构造方法等等,而不做其他的操作时,才能把Lambda表达式重写为方法引用。

如:s->s.length() == 0是一个只有一行逻辑代码的Lambda表达式,但是它不能使用方法引用重写。因此它执行了两步逻辑,首先是求得s的长度,然后判断长度是否为0.

3. 总结

方法引用使用运算符::连接类(或对象)与方法名称(或new)实现在特定场景下lambda表达式的简化表示,使用时要注意方法引用的使用场景及各种方法引用的特性。使用方法引用的好处是能够更进一步简化代码编写,使代码更简洁。但同样的需要注意的是,代码越简化,代码的理解难度也就随之增加,故需要根据具体的场景具体考虑是否使用方法引用。

你可能感兴趣的:(Java)