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
    @AllArgsConstructor
    static 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内部有个逻辑一样的方法,就用它来替换Lambda
        list.sort(Person::compare);
        System.out.println(list);
    }

    @Data
    @AllArgsConstructor
    static 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>() {
            @Override
            public 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>() {
            @Override
            public 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() {
            @Override
            public 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); // false

        MyPredict myPredict3 = MyInteger::compare;
        myPredict3.test(1, 2); // false

        MyPredict 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
    @Setter
    static 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.mfbz.cn/a/199763.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

技巧-PyCharm中Debug和Run对训练的影响和实验测试

简介 在训练深度学习模型时&#xff0c;使用PyCharm的Debug模式和Run模式对训练模型的耗时会有一些区别。 Debug模式&#xff1a;Debug模式在训练模型时&#xff0c;会对每一行代码进行监视&#xff0c;这使得CPU的利用率相对较高。由于需要逐步执行、断点调试、查看变量值等操…

Python hashlib库解析:数据安全加密必备指南

更多资料获取 &#x1f4da; 个人网站&#xff1a;ipengtao.com 哈希函数在计算机科学中扮演着重要的角色。它是一种能够将任意长度的数据转换成固定长度的唯一值的算法。Python提供了hashlib库&#xff0c;用于生成哈希摘要&#xff0c;提供了常见的哈希算法&#xff0c;如MD…

Prometheus的详细部署

普罗米修斯下载网址: Download | Prometheus 准备两台机器&#xff1a; 192.168.58.152 prometheus 192.168.58.142 node_exporter 关闭防火墙和selinux&#xff1a; [rootlocalhost ~]# setenforce 0 && systemctl stop firewalld[rootlocalhost ~]# seten…

Ubuntu:安装Powershell

Powershell的安装与使用&#xff1a; 1&#xff09;安装Powershell&#xff1a;在终端依次运行以下命令即可&#xff1a; $ sudo apt-get update $ sudo apt-get install -y wget apt-transport-https software-properties-common $ wget -q "https://packages.microsof…

基于ArcGIS Pro、R、INVEST等多技术融合下生态系统服务权衡与协同动态分析实践应用

生态系统服务是指生态系统所形成的用于维持人类赖以生存和发展的自然环境条件与效用&#xff0c;是人类直接或间接从生态系统中得到的各种惠益。联合国千年生态系统评估&#xff08;Millennium ecosystem assessment&#xff0c;MA&#xff09;提出生态系统服务包括供给、调节、…

【Vue】绝了!还有不懂生命周期的?

生命周期 Vue.js 组件生命周期&#xff1a; 生命周期函数&#xff08;钩子&#xff09;就是给我们提供了一些特定的时刻&#xff0c;让我们可以在这个周期段内加入自己的代码&#xff0c;做一些需要的事情; 生命周期钩子中的this指向是VM 或 组件实例对象 在JS 中&#xff0c;…

分层理解Java字符串常量池

Java是一门计算机编程语言&#xff0c;但我们脑海中所理解的Java不仅仅是一门语言。它还包括Java虚拟机&#xff08;JVM&#xff09;的一系列规定&#xff0c;及具体Java产品&#xff08;如Hotspot&#xff09;的实现原理。 不管我们日常在Java中用到的任何一种语法&#xff0…

Ubuntu Linux玩童年小霸王插卡游戏

1.下载安装模拟器 在Windows平台模拟器非常多&#xff0c;而且效果也很优秀&#xff0c;Linux平台的用户常常很羡慕&#xff0c;却因为系统的缘故&#xff0c;无法使用这样的模拟器&#xff0c;但是随着时代的发展&#xff0c;Linux平台也出现了许多优秀的模拟器&#xff0c;现…

选择更灵活的设计工具:SOLIDWORKS 软件网络版与单机版的比较

随着科技的飞速发展&#xff0c;工程设计领域对于高效、灵活的设计工具需求日益增加。SOLIDWORKS 作为一款广受欢迎的三维设计软件&#xff0c;提供了网络版和单机版两种选择。在本文中&#xff0c;我们将深入探讨这两个版本的区别&#xff0c;并为您详细介绍它们的价格差异。 …

基于单片机的烟雾检测报警装置(论文+源码)

1.系统设计 &#xff08;1&#xff09;利用传感器实现环境中温度、烟雾浓度的实时检测&#xff1b; &#xff08;2&#xff09;系统检测的各项数据信息通过液晶模块进行显示&#xff0c;提高设计可视化&#xff1b; &#xff08;3&#xff09;系统可以根据实际情况利用按键模…

20天GMV超过百万美金!桌下迷你跑步机在TikTok Shop美国站热销

上周总GMV达到1.59亿美元&#xff0c;达到历史新高&#xff0c;是美国站自开通以来首次单周出单达到亿级&#xff1b;日均出单1660万美元&#xff0c;单日出单最高达2820万美元&#xff1b; 截至11月19日&#xff0c;GMV Top 5 的商品分类排名依次为&#xff1a;美妆个护、女士…

《深入理解计算机系统》学习笔记 - 第三课 - 位,字节和整型

Lecture 03 Bits,Bytes, and Integer count 位&#xff0c;字节&#xff0c;整型 文章目录 Lecture 03 Bits,Bytes, and Integer count 位&#xff0c;字节&#xff0c;整型运算&#xff1a;加&#xff0c;减&#xff0c;乘&#xff0c;除加法乘法取值范围乘法结果 使用无符号注…

交流负载的功能实现原理

交流负载的功能实现原理主要涉及到电力电子技术、电机控制技术和电力系统保护技术等多个方面。 交流负载的功能实现需要通过电力电子器件进行电能的转换和控制&#xff0c;电力电子器件主要包括开关器件和电力电子变压器等。开关器件主要用于实现电能的通断控制&#xff0c;如晶…

消息队列进阶-1.消息队列的应用场景与选型

&#x1f44f;作者简介&#xff1a;大家好&#xff0c;我是爱吃芝士的土豆倪&#xff0c;24届校招生Java选手&#xff0c;很高兴认识大家&#x1f4d5;系列专栏&#xff1a;Spring源码、JUC源码、Kafka原理&#x1f525;如果感觉博主的文章还不错的话&#xff0c;请&#x1f44…

vue el-table表格中每行上传文件(上传简历)操作

1、HTML中 <el-table :data"formInfo.userListDto" border stripe max-height"400"><el-table-column type"index" label"序号" width"50"> </el-table-column><el-table-column prop"realName&q…

【Udemy】AWS CLF - 01 题库 (英文版 + 中文版)目录

【挑战业余一周拿证】CSDN官方课程目录 【挑战业余一周拿证】AWS 认证云从业者 薅200美金羊毛 一、介绍 文章记录题库&#xff08;包含答案解释中文翻译&#xff09; 共计23章&#xff0c;每天更新 2-10 章习题&#xff0c;需要的客观请点赞收藏 来源Udemy&#xff0c;刷题…

Docker容器常用命令

文章目录 启动类命令帮助类命令镜像命令列出本地主机上的镜像在远程仓库中搜索镜像下载镜像保存镜像加载 tar 包为镜像查看占据的空间删除镜像 虚悬镜像命令自动补全新建启动容器启动交互式容器启动守护式容器 列出正在运行的容器容器其他启停操作启动已经停止的容器重启容器停…

【jupyter notebook中插件 nbextensions 安装失败分析与解决方法】

文章目录 问题描述分析与解决总结 问题描述 一开始在安装 notebook 中的插件 nbextensions 时根本没有注意到版本的适配问题&#xff0c;都是进行默认的安装&#xff0c;结果安装是最新版本的 notebook7.x&#xff0c;恰好 notebook7.x 版本不再适应插件 nbextensions&#xf…

智能优化算法应用:基于头脑风暴算法无线传感器网络(WSN)覆盖优化 - 附代码

智能优化算法应用&#xff1a;基于头脑风暴算法无线传感器网络(WSN)覆盖优化 - 附代码 文章目录 智能优化算法应用&#xff1a;基于头脑风暴算法无线传感器网络(WSN)覆盖优化 - 附代码1.无线传感网络节点模型2.覆盖数学模型及分析3.头脑风暴算法4.实验参数设定5.算法结果6.参考…

大数据平台/大数据技术与原理-实验报告--部署ZooKeeper集群和实战ZooKeeper

实验名称 部署ZooKeeper集群和实战ZooKeeper 实验性质 &#xff08;必修、选修&#xff09; 必修 实验类型&#xff08;验证、设计、创新、综合&#xff09; 综合 实验课时 2 实验日期 2023.11.04-2023.11.05 实验仪器设备以及实验软硬件要求 专业实验室&#xff08…
最新文章