Lambda表达式与方法引用

作者简介:大家好,我是smart哥,前中兴通讯、美团架构师,现某互联网公司CTO

联系qq:184480602,加我进群,大家一起学习,一起进步,一起对抗互联网寒冬

引子

先来看一个案例

public class MethodReferenceTest {private static final List<Person> list;static {list = new ArrayList<>();list.add(new Person(19));list.add(new Person(18));list.add(new Person(20));}public static void main(String[] args) {System.out.println(list);// sort()方法是List本身就有的,主要用来排序list.sort((p1, p2) -> p1.getAge() - p2.getAge());System.out.println(list);}@Data@AllArgsConstructorstatic class Person {private Integer age;}}

结果

排序前:

[MethodReferenceTest.Person(age=19), MethodReferenceTest.Person(age=18), MethodReferenceTest.Person(age=20)]

排序后:

[MethodReferenceTest.Person(age=18), MethodReferenceTest.Person(age=19), MethodReferenceTest.Person(age=20)]

把上面的案例稍作改动:

public class MethodReferenceTest {private static final List<Person> list;static {list = new ArrayList<>();list.add(new Person(19));list.add(new Person(18));list.add(new Person(20));}public static void main(String[] args) {System.out.println(list);// 改动2:既然Person内部有个逻辑一样的方法,就用它来替换Lambdalist.sort(Person::compare);System.out.println(list);}@Data@AllArgsConstructorstatic class Person {private Integer age;// 改动1:新增一个方法,逻辑和之前案例的Lambda表达式相同public static int compare(Person p1, Person p2) {return p1.getAge() - p2.getAge();}}
}

嗯?这是什么操作?不急,接着往下看。

从Lambda到方法引用

大家在《Lambda表达式》一文中应该看过下面这段代码:

/*** 从匿名对象 到Lambda 再到方法引用** @author mx*/
public class MethodReferenceTest {public static void main(String[] args) {String str1 = "abc";String str2 = "abcd";// 方式1:匿名对象Comparator<String> comparator1 = new Comparator<String>() {@Overridepublic int compare(String o1, String o2) {return o1.length() - o2.length();}};compareString(str1, str2, comparator1);// 方式2:过渡为Lambda表达式Comparator<String> comparator2 = (String s1, String s2) -> {return s1.length() - s2.length();};compareString(str1, str2, comparator2);// 方式2的改进版:省去赋值操作,直接把整个Lambda表达式作为参数丢进去compareString(str1, str2, (String s1, String s2) -> {return s1.length() - s2.length();});// 方式2的最终版:把变量类型和return也去掉了,因为Java可以自动推断compareString(str1, str2, (s1, s2) -> s1.length() - s2.length());// 方式3:换种比较方式,本质和方式2是一样的,不信你去看看String#compareTo()Comparator<String> comparator3 = (s1, s2) -> s1.compareTo(s2);// 方式4:IDEA提示有改进的写法,最终变成了方法引用compareString(str1, str2, String::compareTo);// 完美。}/*** 传递Comparator,对str1和str2进行比较** @param str1* @param str2* @param comparator*/public static void compareString(String str1, String str2, Comparator<String> comparator) {System.out.println(comparator.compare(str1, str2));}
}

很多初学者肯定崩溃了:Lambda已经够抽象了,好不容易从匿名对象过渡到Lambda,怎么又突然冒出String::compareTo这鬼东西?!

我们在学习Lambda时,把它和匿名类作比较。因为匿名类和Lambda处理的逻辑是一样的,所以就用Lambda简化了匿名类:

同样的,如果项目中已经定义了相同逻辑的方法,我们为什么还要再写一遍呢?即使Lambda表达式再怎么简洁,终究还是要手写好几行代码。

所以,JDK在Lambda表达式的基础上又提出了方法引用的概念,允许我们复用当前项目(或JDK源码)中已经存在的且逻辑相同的方法。

比如上面那个例子中的:

// 方式3:换种比较方式,本质和方式2是一样的
Comparator<String> comparator3 = (s1, s2) -> s1.compareTo(s2);// 方式4:IDEA提示有改进的写法,最终变成了方法引用
compareString(str1, str2, String::compareTo);

String::compareTo看起来形式有点诡异,但这只是一种语法而已,习惯就好了,关键是明白它代表什么意思。Java8引入::符号,用来表示方法引用。所谓的方法引用,就是把方法搬过来使用。那么,String::compareTo把哪个类的什么方法搬过来了呢?

一般来说,String类定义的compareTo方法的正常使用方式是这样的:

public class MethodReferenceTest {public static void main(String[] args) {String str = "hello";String anotherStr = "world";int difference = str.compareTo(anotherStr);}
}

作为更高阶的Lambda表达式,方法引用也能作为参数传递,于是就有了:

public class MethodReferenceTest {public static void main(String[] args) {String str = "hello";String anotherStr = "world";// 匿名内部类Comparator<String> comparator = new Comparator<String>() {@Overridepublic int compare(String str, String anotherStr) {return str.compareTo(anotherStr);}};// 方法引用。上面的str.compareTo(anotherStr)不就是String::compareTo吗!!Comparator<String> newComparator = String::compareTo;compareString(str, anotherStr, newComparator);}/*** 传递Comparator,对str1和str2进行比较** @param str1* @param str2* @param comparator*/public static void compareString(String str1, String str2, Comparator<String> comparator) {System.out.println(comparator.compare(str1, str2));}
}

总之,Java8的意思就是:

兄弟,如果已经存在某个方法能完成你的需求,那么你连Lambda表达式都别写了,直接引用这个方法吧。

但我个人更推荐Lambda表达式,原因有两个:

  • 对初学者而言,Lambda表达式语义更清晰、更好理解
  • Lambda表达式细粒度更小,能完成更精细的需求

第一点,你懂的。

第二点,请容许我来证明一下。

Lambda表达式VS方法引用

/*** MyPredict是模拟Predict* MyInteger是模拟Integer* <p>* 本次测试的目的旨在说明:Lambda毕竟是手写的,自由度和细粒度要高于方法引用。** @author sunting*/
public class MethodAndLambdaTest {public static void main(String[] args) {// 1.匿名对象MyPredict myPredict1 = new MyPredict() {@Overridepublic boolean test(int a, int b) {return a - b > 0;}};boolean result1 = myPredict1.test(1, 2); // false// 2.从匿名对象过渡到Lambda表达式MyPredict myPredict2 = (a, b) -> a - b > 0;myPredict2.test(1, 2); // false// 3.MyInteger#compare()的方法体和上面的Lambda表达式逻辑相同,可以直接引用MyPredict myPredict3 = MyInteger::compare;myPredict3.test(1, 2); // false// 4.Lambda说,你想模仿我?想得美!老子要DIY一下比较规则(a减b 变成了 b减a)MyPredict myPredict4 = (a, b) -> b - a > 0;myPredict4.test(1, 2); // true// 5.看到这,方法引用不服气,也想DIY一把MyPredict myPredict5 = MyInteger::compare;// ???,没法DIY,MyInteger::compare是把整个方法搬过来,不能修改内部的逻辑}
}interface MyPredict {boolean test(int a, int b);
}class MyInteger {public static boolean compare(int a, int b) {return a - b > 0;}
}

方法引用,其实就是把现成的某个方法拿来替代逻辑相似的Lambda表达式。

但Lambda表达式由(a, b) -> a - b > 0 变为 (a, b) -> b - a > 0 ,说明Lambda逻辑已经变了,此时原先的方法引用就不匹配了,不能再用了。此时我们最自然的想法应该是从现成的项目中找到逻辑和(a, b) -> b - a > 0相同的另一个方法,然后把那个方法引用过来,而不是想着改变原来的MyInteger::Compare,那不是你的方法,你也只是借用而已!!

所以,我们给MyInteger加一个方法吧:

class MyInteger {public static boolean compare(int a, int b) {return a - b > 0;}public static boolean anotherCompare(int a, int b) {return b - a > 0;}
}

这样,方法引用的逻辑又和Lambda匹配了:

public class MethodAndLambdaTest {public static void main(String[] args) {MyPredict myPredict2 = (a, b) -> a - b > 0;myPredict2.test(1, 2); // falseMyPredict myPredict3 = MyInteger::compare;myPredict3.test(1, 2); // falseMyPredict myPredict4 = (a, b) -> b - a > 0;myPredict4.test(1, 2); // true// MyInteger::anotherCompare的逻辑和上面的Lambda才是匹配的MyPredict myPredict5 = MyInteger::anotherCompare;myPredict5.test(1, 2); // true}}interface MyPredict {boolean test(int a, int b);
}class MyInteger {public static boolean compare(int a, int b) {return a - b > 0;}public static boolean anotherCompare(int a, int b) {return b - a > 0;}
}

再看一个Stream API的例子:

filter此时需要的逻辑是:年龄大于等于30岁的teacher。

你能从现有项目中找到逻辑为“年龄大于等于30岁的teacher”的方法吗?

答案是没有。

你最多只能调用Teacher::getAge(),但是这个方法引用的逻辑是“获取老师的年龄”,而不是“是否大于等于30岁”,两者逻辑不同,无法替换。

那能不能使用 Teacher::getAge()>=30 呢?

答案是不能。

首先,filter()的参数要么是Lambda表达式,要么是方法引用,不能是方法引用+语句,不伦不类。

其次,也是最重要的,你可以认为Teacher::getAge表示

public Integer getAge(){return this.age;
}

中的return this.age;,它是一个语句。我们可以对表达式叠加判断,比如 a-b ,我们可以继续叠加变成 a-b+c。但是 int d = a-b+c; 已经没办法再叠加了,因为 int d = a-b+c; >= 30 是不可接受的!

处理办法也简单,就是找一个相同逻辑的方法并引用它。假设存在以下方法:

public boolean isBiggerThan30(){return this.age >= 30;
}

那就可以写成:

list.stream().filter(Teacher::isBiggerThan30);

后话

关于方法引用其实还可以展开说,比如可以分为:

  • 静态方法引用(Integer::compare)
  • 实例方法引用(this::getName、user::getName)
  • 构造器方法引用(User::new)

总体来说,方法引用(包括构造器引用)的前提是,函数式接口的方法对应的参数列表和返回值 与 引用类定义的方法的参数列表和返回值 一致。这样说可能比较绕,这里举一个demo:

public class StreamConstructorTest {public static void main(String[] args) {// 下面4个语句都是Person::new,却能赋值给不同的函数式接口// 原因是:每个函数式接口都能从Person类中找到对应的方法(参数列表一致),从而完成方法引用PersonCreatorNoConstruct person1 = Person::new;// 大家可以尝试把Person中Age构造函数注释,那么下面的赋值语句会提示错误,因为此时不存在只有一个age参数的构造器!PersonCreatorWithAge person2 = Person::new;PersonCreatorWithName person3 = Person::new;PersonCreatorAllConstruct person4 = Person::new;}public interface PersonCreatorNoConstruct {// 对应Person无参构造Person create();}public interface PersonCreatorWithAge {// 对应Person的age构造函数Person create(Integer age);}public interface PersonCreatorWithName {// 对应Person的name构造函数Person create(String name);}public interface PersonCreatorAllConstruct {// 对应Person的全参构造函数Person create(Integer age, String name);}@Getter@Setterstatic class Person {private Integer age;private String name;public Person() {}public Person(Integer age) {this.age = age;}public Person(String name) {this.name = name;}public Person(Integer age, String name) {this.age = age;this.name = name;}}
}

但无论是方法引用还是构造器引用,都是细枝末节的东西,本质上学习好Lambda表达式即可。我对方法引用/构造器引用的态度就一个:如果我的代码不是最优,让IDEA提醒我便是,我反正是懒得记~

作者简介:大家好,我是smart哥,前中兴通讯、美团架构师,现某互联网公司CTO

进群,大家一起学习,一起进步,一起对抗互联网寒冬

 

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/200290.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

RPC基础

RPC基础知识 RPC 是什么? RPC&#xff08;Remote Procedure Call&#xff09; 即远程过程调用&#xff0c;通过名字我们就能看出 RPC 关注的是远程调用而非本地调用。 为什么要 RPC &#xff1f; 因为&#xff0c;两个不同的服务器上的服务提供的方法不在一个内存空间&…

Hadoop学习笔记(HDP)-Part.07 安装MySQL

目录 Part.01 关于HDP Part.02 核心组件原理 Part.03 资源规划 Part.04 基础环境配置 Part.05 Yum源配置 Part.06 安装OracleJDK Part.07 安装MySQL Part.08 部署Ambari集群 Part.09 安装OpenLDAP Part.10 创建集群 Part.11 安装Kerberos Part.12 安装HDFS Part.13 安装Ranger …

无人机停机坪的主要功能有哪些

随着无人机行业领域的不断完善&#xff0c;无人机停机坪作为一项关键基础设施&#xff0c;正发挥着越来越重要的作用。无人机停机坪也叫无人机机巢、无人机机库、无人机机场。无人机停机坪不仅是无人机的“家”&#xff0c;更是其高效运行的关键环节。让我们一同探索无人机停机…

1+X Web 前端开发职业技能等级证书模拟题(中级)理论知识

1X Web 前端开发职业技能等级证书模拟题&#xff08;中级&#xff09;理论知识 一、单项选择题 在 Bootstrap 中&#xff0c;可以使用 navbar-header 类的情况是&#xff08;&#xff09; A 为整个页面添加一个标题 B 为导航栏添加一个标题 C 为导航栏 添加头部 D 为整个页面添…

js对象转换为数组的两种方法

第一种方法&#xff1a; Object.values(obj) 示例&#xff1a; var obj { name: 张三, age: 18}; console.log( Object.values(obj) ); // [张三, 18]第二种方法&#xff1a; for-in循环 示例&#xff1a; var obj { name: 张三, age: 18}; var arr []; for(let i in obj) …

探讨电能质量监测与治理解决方案在半导体行业的设计与应用-安科瑞 蒋静

摘要&#xff1a;在国家鼓励半导体材料国产化的政策导向下&#xff0c;本土半导体材料厂商不断提升半导体产品技术水平和研发能力&#xff0c;逐渐打破了国外半导体厂商的垄断格局&#xff0c;推进中国半导体材料国产化进程。半导体产品的制造使用到的设备如单晶炉、多晶炉等都…

Isaac Sim教程01 Isaac Sim介绍

Isaac Sim 介绍 版权信息 Copyright 2023 Herman YeAuromix. All rights reserved.This course and all of its associated content, including but not limited to text, images, videos, and any other materials, are protected by copyright law. The author holds all…

【MySQL】基本安装配置

1 基础知识 1.1 MySQL安装 下载地址&#xff1a;https://dev.mysql.com/downloads/mysql/ 1.1.1 安装过程 配置环境变量&#xff08;和配置Java8的环境变量如出一辙&#xff09;在MySQL解压文件夹下&#xff0c;导入my.ini文件&#xff0c;与bin目录同级&#xff0c;具体文…

流程编排-java

传统编码方式的缺陷 1&#xff1a;做应用开发&#xff0c;传统的编码方式太过僵硬&#xff0c;复用性差&#xff0c;熟悉成本高&#xff0c;无法高速的满足企业的快速发展。 2&#xff1a;当系统做到一定的程度&#xff0c;大多的系统的代码&#xff0c;维护成本&#xff0c;居…

前端并发多个请求并失败重发

const MAX_RETRIES 3;// 模拟请求 function makeRequest(url) {return new Promise((resolve, reject) > {setTimeout(() > {Math.random() < 0.75 ? resolve(${url} 成功) : reject(${url} 失败); // 随机决定请求是否成功}, Math.random() * 2000); // 随机延时执…

oracle sql 把2023/05/06格式化为20230506

oracle sql 把2023/05/06格式化为20230506 在 Oracle SQL 中&#xff0c;你可以使用 TO_CHAR 函数将日期格式化为指定的字符串格式。要将日期 ‘2023/05/06’ 格式化为 ‘20230506’&#xff0c;你可以使用以下查询&#xff1a; SELECT TO_CHAR(TO_DATE(2023/05/06, YYYY/MM/…

七、Linux服务器集群搭建

详见《Linux服务器集群搭建》 【往期回顾】 一、Linux系统概述和安装 二、Linux基础命令 三、Linux高级命令 四、虚拟机网络配置 五、Linux基础软件安装 六、shell编程

vivado时序方法检查1

TIMING-1 &#xff1a; 时钟修改块上的时钟波形无效 在 <cell_type> 输出 <pin_name> 上指定的时钟 <clock_name> 的时钟波形无效 &#xff0c; 与时钟修改块 (CMB) 设置不匹配。该时钟波形为 <VALUE> 。期望的波形为 <VALUE> 。 描述…

JavaScript 原生对象

数组对象 构造函数 Array(); 方法 isArray(); 判断某个值是不是一个数组from(); 将一个类数组的形式转换为一个真正的数组of(); 通过参数得到一个数组 实例 Array.prototype();定义的方法 名称作用参数返回值push在数组的末尾添加一个或者多个新值一个或多个值添加之后数组的…

推荐6款本周 火火火火 的开源项目

本周 GitHub项目圈选 节选自微博、知乎、掘金等社区。 &#x1f525;&#x1f525;&#x1f525;本周推荐的开源项目是&#xff1a; kopia 日常备份工具 screenshot-to-code 截屏生成代码 MiniSearch 全文搜索 clone-voice 声音克隆 NvChad 高颜值终端 DB-GPT-Hub 文本到…

Java中子类都继承父类的什么?

1.构造方法 构造方法不可以被继承的&#xff0c;为什么呢&#xff1f;应为名称的定义&#xff0c;构造方法是一类名称与类名一致&#xff0c;无返回值和类型修饰的一种。所以如果子类继承父类的构造方法的话&#xff0c;那么就违背了构造方法的规定。 2.成员属性 成员属性是…

数据结构和算法专题---3、失效算法与应用

本章我们会对失效算法做个简单介绍&#xff0c;包括常用的失效算法&#xff08;先来先淘汰&#xff08;FIFO&#xff09;、最久未用淘汰&#xff08;LRU&#xff09;、最近最少使用&#xff08;LFU&#xff09;&#xff09;的概述、实现方式、典型场景做个说明。 什么是失效算…

Linux下的java环境搭建

1&#xff0c;安装jdk 上传linux使用的jdk到/opt目录下 解压tar -zxvf文件 配置环境变量 vim /etc/profile 在文件中添加 export JAVA_HOME/opt/jdk8 export PATH$PATH:$JAVA_HOME/bin 使文件生效 source /etc/profile 2,安装tomcat 将tomcat包解压&#xff0c;进入bi…

使用gunicorn部署django项目时,发现静态文件加载失败问题

本文主要介绍如何配置Niginx加载Django的静态资源文件&#xff0c;也就是Static 1、首先需要将Django项目中的Settings.py 文件中的两个参数做以下设置&#xff1a; STATIC_URL /static/ STATIC_ROOT os.path.join(BASE_DIR, static) 2、将 STATICFILES_DIRS [ os.p…

上午面了个腾讯拿 38K 出来的,让我见识到了基础的天花板

今年的校招基本已经进入大规模的开奖季了&#xff0c;很多小伙伴收获不错&#xff0c;拿到了心仪的 offer。 各大论坛和社区里也看见不少小伙伴慷慨地分享了常见的面试题和八股文&#xff0c;为此咱这里也统一做一次大整理和大归类&#xff0c;这也算是划重点了。 俗话说得好…