lambda是java8的新特性,基本使用比较容易理解,但有一个环节遇到了坎儿,那就是方法引用,尤其是类的实例方法引用,烧脑之后总结一下。

在需要函数参数的方法中,我们可以把另一个同类型的方法直接传入,这称为方法引用的绑定。类似于C语言中的函数指针。

lambda表达式可以替代方法引用;或者说方法引用是lambda的一种特例,方法引用不可以控制传递参数。

4.1) 构造器引用

private Person construntorRef(Supplier<Person> sup){Person p=sup.get();return p;
}@Test
public void testConstructorRef(){Person p=construntorRef(Person::new);System.out.println(p);
}

需要有无参的构造器。

4.2) 静态方法引用

    private static void print(String s){System.out.println(s);}@Testpublic void testStaticRef(){Arrays.asList("aa","bb","cc").forEach(TestMethodReference::print);}

so easy,只要静态方法的参数列表和FI需要的参数一致就可以。

4.3) 成员方法引用

@Testpublic void testMemberMethodRef(){Arrays.asList("aa","bb","cc").forEach(System.out::println);}

so easy,只要成员方法的参数列表和FI需要的参数一致就可以。

4.4) 类的任意对象的实例方法引用(很怪异)

@Test
public void testClassMemberMethodRef(){String[] strs={"zzaa","xxbb","yycc"};Arrays.sort(strs,String::compareToIgnoreCase);//OKSystem.out.println(Arrays.asList(strs));File[] files = new File("C:").listFiles(File::isHidden); // OK
}

��,前方高能,请关掉耳机的音乐,认真思考,小心行事。

传统的java开发中,是不允许使用类名去调用成员方法的,这是一个基本原则,那么这里的这种写法就有点不太容易理解了。还是用实例说明:

用到的内部类:

import lombok.Data;@Data
public 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);}
}

测试代码:

@Testpublic 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);//② okbean1.test1(TestBean2::expect1);//③bean1.test1(TestBean2::expect2);//④TestBean2 bean2=new TestBean2();bean2.test1(TestBean1::expect1);//⑤bean2.test1(TestBean1::expect2);//⑥ okbean2.test1(TestBean2::expect1);//⑦bean2.test1(TestBean2::expect2);//⑧}
}

测试方法中,除了标记OK的行正确,其他都报错。

分析:

首先我们要明确FI需要的参数列表是:(TestBean1,TestBean2)

  1. 我们先看①行,我们传入的”::”前导的类是TestBean1,而expect1方法匹配的是TestBean1类型的入参bean1,也就是说省略了TestBean2类型的参数bean2,FI中的最后一个参数。即便我们使用类TestBean1去new一个对象,也找不到TestBean2,因此这个错误。

  2. 我们先看②行,我们传入的”::”前导的类是TestBean1,而expect2方法匹配的是TestBean2类型的入参bean2,也就是说省略了TestBean1类型的参数bean1,那么lambda就可以使用”::”前导的TestBean1构建一个对象,作为第一个参数,从而匹配FI的接口方法。ok。

  3. 我们先看③行,我们传入的”::”前导的类是TestBean2,而expect1方法匹配的是TestBean1类型的入参bean1,也就是说省略了TestBean2类型的参数bean2,FI的最后一个参数。按照第二步的分析,我们用”::”前导的类TestBean2去new一个对象,应该可以凑足两个参数。实际测试会发现这不灵。这就证明了只能省略第一个参数,而且,用”::”前导的类也必须是第一个参数的类型。

  4. 同第一步类似,第④行代码,找不到TestBean1的参数,有错误可以理解。

  5. 至于⑤~⑧,只是替换了外层的test1的主体,没有任何区别。这证明了,lambda的匹配与外层是什么鬼没有任何关系,它只关心外层需要的FI的参数列表。

  6. 请不要看下一步,在这里停下来冷静的思考一下,如果我们把TestInterface中FI方法的参数位置换一下,即public void anyStringAsName(TestBean2 cat,TestBean1 dog);,结果应该是哪两行正确呢?认真思考一下,实在想不明白跑一下测试用例,也许对理解更有帮助。

  7. 如果想明白了用这个思路验证一下:参照参数列表(TestBean2,TestBean1),可以确定只可以省略第一个参数即TestBean2,那么”::”签到必须是TestBean2,用于自动创建对象;而未省略的参数是TestBean1,那么方法名为expect1,结果为xxx(TestBean2::expect1),即③和⑦,你答对了吗?

lambda方法引用总结——烧脑吃透相关推荐

  1. 3. lambda 方法引用

    lambda 方法引用分为4类,方法引用也受到访问控制权限的限制,可以通过在引用位置是否能够调用被引用方法来判断.具体分类信息如下: 类型 使用方式 静态方法 ContainingClass::sta ...

  2. Lambda方法引用

    1.Lambda表达式 1.1体验Lambda表达式[理解] 案例需求 启动一个线程,在控制台输出一句话:多线程程序启动了 实现方式一 实现步骤 定义一个类MyRunnable实现Runnable接口 ...

  3. 31.3 Java进阶之lambda方法引用

    文章目录 1.方法引用 1.1 复用已有方法传递lambda 1.2 : :操作符 1.2.1 object::instanceMethod 1.2.2 Class::staticMethod 1.2 ...

  4. 【Java 进阶】匿名类(代码传递、回调、过滤器)、Lambda表达式(方法引用)、函数式接口(Supplier、Consumer、Predicate、Function)

    匿名类 匿名类(Anonymous Class) 匿名类的使用注意 匿名类 - 代码传递 - 测试代码运行时间的工具类 匿名类 - 回调 - 简易网络请求 匿名类 - 过滤器 - 获取目录下的所有文件 ...

  5. jdk8新特性-Lambda表达式,方法引用

    Lambda方法引用的概念 1).什么是"方法引用":当我们使用Lambda实现一些功能时,发现已有的类库中已经有方法实现了这样的功能,这时,我们就可以引用已实现的方法来代替Lam ...

  6. java写方法用来调用_Java从入门到入土(79)lambda表达式和方法引用

    lambda表达式是Java8引入的新功能.lambda表达式以字面量的形式把少量代码直接写在程序中,从而让 Java 编程更符合函数式风格(Java 实质上是面向对象语言.不过,引入lambda 表 ...

  7. 深入理解Java 8 Lambda(语言篇——lambda,方法引用,目标类型和默认方法)

    作者:Lucida 微博:@peng_gong 豆瓣:@figure9 原文链接:http://zh.lucida.me/blog/java-8-lambdas-insideout-language- ...

  8. Java 8 - 06 Lambda 和方法引用实战

    文章目录 Pre 第 1 步:传递代码 第 2 步:使用匿名类 第 3 步:使用 Lambda 表达式 第 4 步:使用方法引用 Pre 前几篇文章,我们已经学习了行为参数化.匿名类.Lambda表达 ...

  9. Java8 之 lambda 表达式、方法引用、函数式接口、默认方式、静态方法

    今天我来聊聊 Java8 的一些新的特性,确实 Java8 的新特性的出现,给开发者带来了非常大的便利,可能刚刚开始的时候会有点不习惯的这种写法,但是,当你真正的熟悉了之后,你一定会爱上这些新的特性的 ...

最新文章

  1. 正确生成浮点型的方法,解决sqlachemy Float浮点型的坑,生成float类型时,长度和精度均为0,导致查询不到结果!...
  2. (chap1 网络基础知识)网络的构成要素:(7)网关
  3. JEECG整合finereport快速搭建与开发
  4. 信息学奥赛一本通 1149:最长单词2 | OpenJudge NOI 1.13 16
  5. c# list集合根据某个字段去重_java8 List 根据对象某个字段或多个字段去重、筛选、List转Map、排序、分组、统计计数等等...
  6. selenium的定位方式
  7. syntax error: unrecognized tag:
  8. dict 转换成json_Python XML转换为JSON,XML转换为Dict
  9. 大文件上传 之 改版了的SlickUpload.HttpUploadModule(Krystalware.SlickUpload.dll)
  10. map.entryk,v小用法(转)
  11. JDK8下载 (jdk-8u271-windows-x64和jdk-8u271-linux-x64.tar)
  12. c语言输出26个小写英文字母,c语言题。 按顺序打印输出26个英文字母,
  13. 逆向脱壳-fsg手动脱壳
  14. JAVA从入门到精通(2)
  15. java项目设计与思路
  16. 以人为本 体验至上(三)
  17. 第4章 数据的概括性度量
  18. 研华USB4761 C#编程
  19. 雨,百家讲坛,孔庆东,算法
  20. Linux CentOS 7 在DNS服务器上配置转发器

热门文章

  1. Java 泛型中的? super T和? extends T
  2. 根据屏幕大小制定显示页面
  3. AngularJS开发指南
  4. suse 10 下mysql安装
  5. 装了xcode3.2 后再装xcode4.1 出现的问题 永远build不过
  6. 测试经理管理团队应注意的点
  7. dll放在unity哪个文件夹下_程序丨如何将你的Unity代码整理到一个DLL中?
  8. docker 搭建 web_《SpringBoot+Dubbo+Zookeeper整合搭建简单的分布式应用》
  9. python一次性输入多个数_python如何一次性输入多个数
  10. 没有基础怎么学习Web前端?相关学习路线又是什么?