lambda方法引用总结——烧脑吃透
来源:互联网 发布:校园招聘java面试题库 编辑:程序博客网 时间:2024/06/05 14:10
lambda是java8的新特性,基本使用比较容易理解,但有一个环节遇到了坎儿,那就是方法引用,尤其是类的实例方法引用,烧脑之后总结一下。
在需要函数参数的方法中,我们可以把另一个同类型的方法直接传入,这称为方法引用的绑定。类似于C语言中的函数指针。
lambda表达式可以替代方法引用;或者说方法引用是lambda的一种特例,方法引用不可以控制传递参数。
4.1) 构造器引用
private Person construntorRef(Supplier<Person> sup){ Person p=sup.get(); return p;}@Testpublic void testConstructorRef(){ Person p=construntorRef(Person::new); System.out.println(p);}
需要有无参的构造器。
4.2) 静态方法引用
private static void print(String s){ System.out.println(s); } @Test public void testStaticRef(){ Arrays.asList("aa","bb","cc").forEach(TestMethodReference::print); }
so easy,只要静态方法的参数列表和FI需要的参数一致就可以。
4.3) 成员方法引用
@Test public void testMemberMethodRef(){ Arrays.asList("aa","bb","cc").forEach(System.out::println); }
so easy,只要成员方法的参数列表和FI需要的参数一致就可以。
4.4) 类的任意对象的实例方法引用(很怪异)
@Testpublic void testClassMemberMethodRef(){ String[] strs={"zzaa","xxbb","yycc"}; Arrays.sort(strs,String::compareToIgnoreCase);//OK System.out.println(Arrays.asList(strs)); File[] files = new File("C:").listFiles(File::isHidden); // OK}
��,前方高能,请关掉耳机的音乐,认真思考,小心行事。
传统的java开发中,是不允许使用类名去调用成员方法的,这是一个基本原则,那么这里的这种写法就有点不太容易理解了。还是用实例说明:
用到的内部类:
import lombok.Data;@Datapublic static class Person{ private String name; private Integer age; public int mycompare(Person p1){ return p1.getAge()-this.getAge(); } public void print(){ System.out.println(this); } public void println(Person p){ System.out.println(p); } public int compareByAge(Person a,Person b){ return a.getAge().compareTo(b.getAge()); }}public static class APerson{ public void print(){ System.out.println(this); } public void println(Person p){ System.out.println(p); }}
测试代码:
@Test public void testClassMemberMethodRef2() { // R apply(T t);//要求一个参数 Function<String, String> upperfier1 = String::toUpperCase; UnaryOperator<String> upperfier2 = (x) -> x.toUpperCase();//这里没有参数,即0个 /* * 小结:如果方法引用表达式 "String::toUpperCase" 可以用lambda表达式中参数的指定成员方法(这个成员方法的参数比FI要求的参数少一个改类型的参数)改写, * 那么就可以使用 "类的实例方法"来表示方法引用。 * * 或者说:如果lambda表达式的lambda体中使用的方法是参数匹配的方法,那么方法引用表达式就用"类引用对象的实例方法"。 * * lambda的参数是方法的主体。 */ class Print { public void println(String s) { System.out.println(s); } } // void accept(T t); Consumer<String> sysout1 = new Print()::println; Consumer<String> sysout2 = (x) -> new Print().println(x); /* * 小结:如果方法引用表达式 "new Print()::println" 可以用lambda表达式中参数的具体对象的参数匹配的成员方法改写, * 那么就用 "对象的实例方法"来表示方法引用。 * * 或者说:如果lambda表达式的lambda体中使用的方法来操作lambda的参数,那么方法引用表达式就用"对象的实例方法"。 * * lambda的参数是方法的参数。 */ //有一个更让人易混淆的例子,可以用上面的规则来验证,Arrays.sort(T t,Comparator<? extends t> c) class Person { public int com1(Person p) { return 1; } public int com2(Person p1, Person p2) { return 1; } } // int compare(T o1, T o2);//需要两个参数 Person【】 ps = { new Person(), new Person() }; Arrays.sort(ps, Person::com1); Arrays.sort(ps, (x,y)->x.com1(y)); Arrays.sort(ps, new Person()::com2); Arrays.sort(ps, (x,y)->new Person().com2(x, y)); //按照以上规则验证应该能说明清楚。 /* * 但是一个接口为什么有两种写法?缺省的lambda会匹配FI方法,即"int compare(T o1, T o2);" * 从上面的lambda表达式来分析,默认的使用lambda应该是: */ Comparator<Person> comparator1 = new Person()::com2; /* * 下面的方式又是怎么回事呢? */ Comparator<Person> comparator2 = Person::com1; System.out.println(comparator2); /* * 任一个两个参数的FI接口方法(int compare(T o1, T o2)),都可以用引用减少一个参数的方法(int o1<T>.compare(T o2))来代替,而引用对象本身作为另一个隐含参数,那么方法引用的对象用类名,表示类的任意对象。 还是有点乱?我们来换一个角度来看一下: 首先,我们需要的是int compare(T o1, T o2)是两个参数; 其次,先不考虑::前缀是类还是对象,你给了我一个compare(T o2),少一个参数?怎么办? lambda机制为了解决这个问题,它使用::前面的类名new一个对象,当做需要的缺少的那个参数,这就是类的实例方法。 */ }
小结一下:
首先明确此处需要的方法参数列表,此处标记参数个数为N,那么:1. 如果传入的方法是一个类型的静态方法,而且参数匹配,使用“类的静态方法引用”;这应该不难理解。2. 如果传入的方法是一个实例的成员方法,而且参数匹配,使用“实例的成员方法”;这也应该不难理解。3. 如果传入的方法是一个类型T的实例的成员方法,而且参数为N-1个,缺少了一个T类型的参数,那么就使用“T类型的实例方法”。
烧脑分析类的实例方法省略了哪个参数
前面的例子,FI的两个参数是同一个类型,如果类型不同呢?省略了哪个参数呢?
是按照位置省略了第一个,亦或者是省略了最后一个?
还是按照类型自动去对应,而不关心第几个呢?
这个时候,我们能想到的办法可能是去看源码,但是一般看代码没有个把礼拜甚至更长,毛都看不出来。我们还是用一个例子来分析一下吧。
定义一个FI接口:
package com.pollyduan.fi;public interface TestInterface { //随便什么名字,lambda并不关心,因为FI只有一个接口,并且根据参数来匹配 public void anyStringAsName(TestBean1 bean1,TestBean2 bean2);}
编写两个用于参数的类:
TestBean1.java
package com.pollyduan.fi;public class TestBean1 { public void expect1(TestBean1 bean1){ } public void expect2(TestBean2 bean2){ } public void test1(TestInterface i){ }}
TestBean2.java
package com.pollyduan.fi;public class TestBean2 { public void expect1(TestBean1 bean1){ } public void expect2(TestBean2 bean2){ } public void test1(TestInterface i){ }}
二者区别不大。
编写测试类:
package com.pollyduan.fi;public class TestFIMain { public static void main(String[] args) { TestBean1 bean1=new TestBean1(); bean1.test1(TestBean1::expect1);//① bean1.test1(TestBean1::expect2);//② ok bean1.test1(TestBean2::expect1);//③ bean1.test1(TestBean2::expect2);//④ TestBean2 bean2=new TestBean2(); bean2.test1(TestBean1::expect1);//⑤ bean2.test1(TestBean1::expect2);//⑥ ok bean2.test1(TestBean2::expect1);//⑦ bean2.test1(TestBean2::expect2);//⑧ }}
测试方法中,除了标记OK的行正确,其他都报错。
分析:
首先我们要明确FI需要的参数列表是:(TestBean1,TestBean2)
我们先看①行,我们传入的”::”前导的类是TestBean1,而expect1方法匹配的是TestBean1类型的入参bean1,也就是说省略了TestBean2类型的参数bean2,FI中的最后一个参数。即便我们使用类TestBean1去new一个对象,也找不到TestBean2,因此这个错误。
我们先看②行,我们传入的”::”前导的类是TestBean1,而expect2方法匹配的是TestBean2类型的入参bean2,也就是说省略了TestBean1类型的参数bean1,那么lambda就可以使用”::”前导的TestBean1构建一个对象,作为第一个参数,从而匹配FI的接口方法。ok。
我们先看③行,我们传入的”::”前导的类是TestBean2,而expect1方法匹配的是TestBean1类型的入参bean1,也就是说省略了TestBean2类型的参数bean2,FI的最后一个参数。按照第二步的分析,我们用”::”前导的类TestBean2去new一个对象,应该可以凑足两个参数。实际测试会发现这不灵。这就证明了只能省略第一个参数,而且,用”::”前导的类也必须是第一个参数的类型。
同第一步类似,第④行代码,找不到TestBean1的参数,有错误可以理解。
至于⑤~⑧,只是替换了外层的test1的主体,没有任何区别。这证明了,lambda的匹配与外层是什么鬼没有任何关系,它只关心外层需要的FI的参数列表。
请不要看下一步,在这里停下来冷静的思考一下,如果我们把TestInterface中FI方法的参数位置换一下,即
public void anyStringAsName(TestBean2 cat,TestBean1 dog);
,结果应该是哪两行正确呢?认真思考一下,实在想不明白跑一下测试用例,也许对理解更有帮助。如果想明白了用这个思路验证一下:参照参数列表
(TestBean2,TestBean1)
,可以确定只可以省略第一个参数即TestBean2,那么”::”签到必须是TestBean2,用于自动创建对象;而未省略的参数是TestBean1,那么方法名为expect1,结果为xxx(TestBean2::expect1)
,即③和⑦,你答对了吗?
- lambda方法引用总结——烧脑吃透
- lambda表达式:方法引用
- 深入理解Java 8 Lambda(语言篇——lambda,方法引用,目标类型和默认方法)
- 深入理解Java 8 Lambda(语言篇——lambda,方法引用,目标类型和默认方法)
- 深入理解Java 8 Lambda(语言篇——lambda,方法引用,目标类型和默认方法)(转载)
- 深入理解Java 8 Lambda(语言篇——lambda,方法引用,目标类型和默认方法)
- 深入理解Java 8 Lambda(语言篇——lambda,方法引用,目标类型和默认方法)
- 深入理解Java 8 Lambda(语言篇——lambda,方法引用,目标类型和默认方法)
- 深入理解Java 8 Lambda(语言篇——lambda,方法引用,目标类型和默认方法)
- 深入理解Java 8 Lambda(语言篇——lambda,方法引用,目标类型和默认方法)
- 深入理解Java 8 Lambda(语言篇——lambda,方法引用,目标类型和默认方法)
- 深入理解Java 8 Lambda(语言篇——lambda,方法引用,目标类型和默认方法)
- Java Lambda(语言篇——lambda,方法引用,目标类型,默认方法,函数接口,变量捕获)
- 深入理解Java 8 Lambda(语言篇——lambda,方法引用,目标类型和默认方法)
- 深入理解Java 8 Lambda(语言篇——lambda,方法引用,目标类型和默认方法
- 深入理解 Java 8 Lambda(语言篇——lambda,方法引用,目标类型和默认方法)
- 【转载】深入理解Java 8 Lambda(语言篇——lambda,方法引用,目标类型和默认方法)
- Java 8 新特性:Lambda 表达式之方法引用(Lambda 表达式补充版)——诺诺"涂鸦"记忆
- poi给合并的单元格设置边框
- 函数调用的几点说明:
- Heavy Transportation POJ
- 卓伟等低俗追星账号被关 关闭账号名单曝光
- input标签点击移除默认值
- lambda方法引用总结——烧脑吃透
- leetcode 196. Delete Duplicate Emails
- 每秒百万级高效C++异步日志实践
- Servlet
- 同步锁效率低的例子
- rand()和srand()函数
- android:内存泄露与内存溢出区别、内存泄露定位
- LINUX摄像驱动三:从零开始写虚拟驱动
- Codeforces Round #418 (Div. 2) -- C. An impassioned circulation of affection(DP预处理)