概述
如果你对将Lambda表达式转换成对应的方法引用有疑惑的话,本文你值得一看。
方法引用(MethodReference)是Lambda表达式的另一种格式,在某些场景下可以提高代码的可读性,那么如何将一个Lambda表达式替换成MethodReference呢?
使用条件
只可以替换单方法的Lambda表达式
什么意思呢 ?
例如下面这个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);}@Overridepublic 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>() {@Overridepublic 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); }
其中isBiggerThan3 是TestUtil类的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>() {@Overridepublic String apply(Student student, String s) {return student.getStatus(s);}};//lambdaBiFunction<Student, String, String> f2 = (student, s) -> student.getStatus(s);//method referenceBiFunction<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>() {@Overridepublic 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>() {@Overridepublic 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>() {@Overridepublic void accept(String s) {System.out.println(s);} };//lambda表达式 Consumer<String> consumer2 = ;//方法引用 Consumer<String> consumer3 = ;
