秒懂Java之方法引用(method reference)详解

【版权申明】非商业目的注明出处可自由转载
博文地址:https://blog.csdn.net/ShuSheng0007/article/details/107562812
出自:shusheng007

相关文章:
秒懂Java之深入理解Lambda表达式

概述

如果你对将Lambda表达式转换成对应的方法引用有疑惑的话,本文你值得一看。

方法引用(MethodReference)是Lambda表达式的另一种格式,在某些场景下可以提高代码的可读性,那么如何将一个Lambda表达式替换成MethodReference呢?有的同学说了,可以使用IDE协助转换,我只能说你太机智了,那这篇文章不是为你准备的。

使用条件

只可以替换单方法的Lambda表达式

什么意思呢 ?

例如下面这个Lambda表达式就不可以使用方法引用替换,因为其不是单方法的,有好几行呢。

Predicate<Integer> p2 = integer -> {
     System.out.println("中国股市是吃屎长大的");
     return TestUtil.isBiggerThan3(integer);
 };

下面这个就可以使用方法引用替换了

Predicate<Integer> p2 = integer -> TestUtil.isBiggerThan3(integer);

使用场景

当使用方法引用替换Lambda表达式具有更好的可读性时,考虑使用。

如何使用

曾几何时,我对方法引用的理解很模糊,一般是使用IDE协助转换,但是转换后的写法很多我都不明白:

Lambda的参数哪去了?
为什么::前面有的是类名称,有的是实例对象呢?
不是只有静态方法::前面才使用类名吗,怎么有的实例方法也使用类名啊?
为什么 ClassName::new 有时要求无参构造器,有时又要求有参构造器呢?构造器参数由什么决定呢?

结论其实显而易见了,我对方法引用的理解根本就是一团浆糊!如果你也有上面的疑问,也许应该接着往下看。

方法引用的类型

解决纷繁复杂信息的最好方式就是分类,这里也不例外。
方法引用可以分为分四类,只要掌握了类型区别一切就变得易如反掌了。
为行文方便,这里先列出要使用的类:

TestUtil里面有一个静态方法,一个实例方法。Student是一个普通实体类,具体如下代码所示

public class MethodReference {
	...
  
  //示例类
  public static class TestUtil {
        public static boolean isBiggerThan3(int input) {
            return input > 3;
        }

        public void printDetail(Student student) {
            System.out.println(student.toString());
        }
    }

  public static class Student {
        private String name;
        private int age;

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

        public String getStatus(String thing) {
            return String.format("%d岁的%s正在%s", age, name, thing);
        }

        @Override
        public String toString() {
            return "Student{" +
                    "name='" + name + '\'' +
                    ", age=" + age +
                    '}';
        }
    }
}

调用类的静态方法

Lambda表达式的那个单方法是某个类的静态方法

有如下格式,args是参数,可以是多个,例如(a1,a2,a3)

lambda:

(args) -> Class.staticMethod(args)

method reference

Class::staticMethod

符合上面形式的调用,不管有多少参数,都省略掉,编译器自动会帮我们传入

实例:

public void testStaticMethodRef() {
    //匿名内部类形式
    Predicate<Integer> p1 = new Predicate<Integer>() {
        @Override
        public boolean test(Integer integer) {
            return TestUtil.isBiggerThan3(integer);
        }
    };
    //lambda表达式形式
    Predicate<Integer> p2 = integer -> TestUtil.isBiggerThan3(integer);
    //MethodReference形式
    Predicate<Integer> p3 = TestUtil::isBiggerThan3;

    Stream.of(1, 2, 3, 4, 5).filter(p3).forEach(System.out::println);
}

其中isBiggerThan3TestUtil类的static方法。从上面的代码你可以清晰的看到,方法从匿名类Lambda再到方法引用的演变。

调用传入的实例的方法

lambda:

(obj, args) -> obj.instanceMethod(args)

method reference

ObjectType::instanceMethod

看到我们lambda的入参obj了吗?它是一个类型,假设为ObjectType,的实例对象。然后再看lambda表达式,是在调用此实例obj的方法。这种类型的lambda就可以写成上面的形式了,看起来和静态方法那个一样。

我们来举个栗子:

public void testInstanceMethodRef1() {
   //匿名类
    BiFunction<Student, String, String> f1 = new BiFunction<Student, String, String>() {
        @Override
        public String apply(Student student, String s) {
            return student.getStatus(s);
        }
    };
    
    //lambda
    BiFunction<Student, String, String> f2 = (student, s) -> student.getStatus(s);
    
	//method reference
    BiFunction<Student, String, String> f3 = Student::getStatus;

    System.out.println(getStudentStatus(new Student("erGouWang", 18), "study", f3));
}
private String getStudentStatus(Student student, String action, BiFunction<Student, String, String> biFunction) {
    return biFunction.apply(student, action);
}

调用已经存在的实例的方法

lambda:

(args) -> obj.instanceMethod(args)

method reference

obj::instanceMethod

我们观察一下我们lambda表达式,发现obj对象不是当做参数传入的,而是已经存在的,所以写成方法引用时就是实例::方法.

举个栗子:

public void testInstanceMethodRef2() {
    TestUtil utilObj = new TestUtil();
	//匿名类
    Consumer<Student> c1 = new Consumer<Student>() {
        @Override
        public void accept(Student student) {
            utilObj.printDetail(student);
        }
    };
    //Lambda表达式
    Consumer<Student> c2 = student -> utilObj.printDetail(student);
	//方法引用
    Consumer<Student> c3 = utilObj::printDetail;
	//使用
    consumeStudent(new Student("erGouWang", 18), c3);
}

private void consumeStudent(Student student, Consumer<Student> consumer) {
    consumer.accept(student);
}

可见utilObj对象是我们提前new出来的,是已经存在了的对象,不是Lambda的入参。

调用类的构造函数

lambda:

(args) -> new ClassName(args)

method reference

ClassName::new

当lambda中的单方法是调用某个类的构造函数,我们就可以将其写成如上形式的方法引用

举个栗子

public void testConstructorMethodRef() {
    BiFunction<String, Integer, Student> s1 = new BiFunction<String, Integer, Student>() {
        @Override
        public Student apply(String name, Integer age) {
            return new Student(name, age);
        }
    };
	//lambda表达式
    BiFunction<String, Integer, Student> s2 = (name, age) -> new Student(name, age);
	//对应的方法引用
    BiFunction<String, Integer, Student> s3 = Student::new;
	//使用
    System.out.println(getStudent("cuiHuaNiu", 20, s3).toString());
}

private Student getStudent(String name, int age, BiFunction<String, Integer, Student> biFunction) {
    return biFunction.apply(name, age);
}

上面代码值得注意的就是,Student类必须有一个与lambda入参相匹配的构造函数。例如此例中,(name, age) -> new Student(name, age); 需要两个入参的构造函数,为什么呢?

因为我们的lambda表达式的类型是 BiFunction,而其正是通过两个入参来构建一个返回的。其签名如下:

@FunctionalInterface
public interface BiFunction<T, U, R> {
	  R apply(T t, U u);
	  ...
}

通过入参(t,u)来生成R类型的一个值。

我们可以写一个如下的方法引用

Function<String, Student> s4 = Student::new;

但是IDE就会报错,提示我们的Student类没有对应的构造函数,我们必须添加一个如下签名的构造函数才可以

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

总结

熟悉了以上四种类型后,方法引用再也难不住你了!留个作业:

Consumer<String> consumer1 = new Consumer<String>() {
    @Override
    public void accept(String s) {
        System.out.println(s);
    }
};
//lambda表达式
Consumer<String> consumer2 = ;
//方法引用
Consumer<String> consumer3 = ;

上面代码中的consumer2consumer3分别是什么呢?其属于方法引用的哪一类呢?请在评论区提交答案。

古之立大事者,不惟有超世之才,亦必有坚忍不拔之志。——苏轼

你可以从Gitbub上找到本文源码:ToMasterJava

引用文章:
https://www.codementor.io/@eh3rrera/using-java-8-method-reference-du10866vx

猜你喜欢

转载自blog.csdn.net/ShuSheng0007/article/details/107562812