[版权申明]非商业目的注明出处可自由转载
出自:shusheng007
相关文章:
秒懂Java之深入理解Lambda表达式
概述
如果你对将Lambda表达式转换成对应的方法引用有疑惑的话,本文你值得一看。
方法引用(MethodReference)是Lambda表达式的另一种格式,在某些场景下可以提高代码的可读性,那么如何将一个Lambda表达式替换成MethodReference呢?有的同学说了,可以使用IDE协助转换,我只能说你太机智了,那这篇文章不是为你准备的。
使用条件
只可以替换单方法的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);
}
@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);
}
其中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>() {
@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 = ;
上面代码中的consumer2
和consumer3
分别是什么呢?其属于方法引用的哪一类呢?请在评论区提交答案。
古之立大事者,不惟有超世之才,亦必有坚忍不拔之志。——苏轼
你可以从Gitbub上找到本文源码:ToMasterJava
引用文章:
https://www.codementor.io/@eh3rrera/using-java-8-method-reference-du10866vx