Lambda表达式与方法引用

news2024/11/19 7:21:40
作者简介:大家好,我是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.coloradmin.cn/o/1269920.html

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

相关文章

什么是供应链攻击?

随着企业越来越依赖技术、连接性和第三方&#xff0c;供应链攻击变得越来越普遍。这些攻击旨在通过供应商和业务合作伙伴损害公司。 供应链攻击可能对企业和组织构成重大威胁&#xff0c;损害其安全以及向客户提供的产品和服务的安全。 在本文中&#xff0c;我们将探讨供应链…

排序算法基本原理及实现2

&#x1f4d1;打牌 &#xff1a; da pai ge的个人主页 &#x1f324;️个人专栏 &#xff1a; da pai ge的博客专栏 ☁️宝剑锋从磨砺出&#xff0c;梅花香自苦寒来 &#x1f324;️冒泡排序 &#x1…

37 - 数据库参数设置优化,失之毫厘差之千里

MySQL 是一个灵活性比较强的数据库系统&#xff0c;提供了很多可配置参数&#xff0c;便于我们根据应用和服务器硬件来做定制化数据库服务。如果现在让你回想&#xff0c;你可能觉得在开发的过程中很少去调整 MySQL 的配置参数&#xff0c;但我今天想说的是我们很有必要去深入了…

CONTROLLING VISION-LANGUAGE MODELS FOR MULTI-TASK IMAGE RESTORATION

CONTROLLING VISION-LANGUAGE MODELS FOR MULTI-TASK IMAGE RESTORATION (Paper reading) Ziwei Luo, Uppsala University, ICLR under review(6663), Cited:None, Stars: 350, Code, Paper. 1. 前言 像CLIP这样的视觉语言模型已经显示出对零样本或无标签预测的各种下游任务…

血的教训--kail系统免密centos7的坑【高版本ssh免密低版本ssh的坑】

血的教训–kail系统免密centos7的坑【高版本ssh免密低版本ssh的坑】 最近下载了一个2023版本的kail系统&#xff0c;但是经过几次设置免密后&#xff0c;ssh过去一直让提供密码&#xff0c;所以就仔细的分析了一下&#xff0c;果然还是发现了点猫腻 接上一个博客&#xff0c;大…

TZOJ 1373 求多项式的和

答案&#xff1a; #include <stdio.h> int main() {int m 0;scanf("%d", &m); // 读取测试实例的个数 while (m--) //循环m次{int n 0, i 0;scanf("%d", &n); // 读取求和项数n double sum 0.0;for (i 1; i < n; i) //分…

项目demo —— GPT 聊天机器人

本文介绍我的开源项目 TelegramChatBot&#xff0c;这是一个基于 OpenAI GPT API 开发的 telegram 机器人&#xff0c;具有多模态交互能力&#xff0c;求 star&#xff01;感谢大家&#xff01;在 telegram jokerController_bot 立即体验&#xff01;欢迎对 GPT 应用开发或对 t…

Web安全漏洞分析-XSS(下)

随着互联网的迅猛发展&#xff0c;Web应用的普及程度也愈发广泛。然而&#xff0c;随之而来的是各种安全威胁的不断涌现&#xff0c;其中最为常见而危险的之一就是跨站脚本攻击&#xff08;Cross-Site Scripting&#xff0c;简称XSS&#xff09;。XSS攻击一直以来都是Web安全领…

【Python】yaml.safe_load()函数详解和示例

在Python中&#xff0c;PyYAML库提供了对YAML&#xff08;YAML Ain’t Markup Language&#xff09;文件的强大支持。YAML是一种直观的数据序列化标准&#xff0c;可以方便地存储和加载配置文件、数据日志等。 yaml.safe_load和yaml.load是Python的PyYAML库提供的两个函数&…

Node.js案例 - 记账本

目录 项目效果 项目的搭建 ​编辑 响应静态网页 ​编辑 ​编辑 结合MongoDB数据库 结合API接口 进行会话控制 项目效果 该案例实现账单的添加删除查看&#xff0c;用户的登录注册。功能比较简单&#xff0c;但是案例主要是使用前段时间学习的知识进行实现的&#xff0c…

Java后端开发——SpringMVC商品管理程序

Java后端开发——SpringMVC商品管理程序 今日目标 Spring MVC框架介绍掌握SpringMVC的核心类的原理及配置掌握SpringMVC的常用注解掌握SpringMVC的增删改查编程 Spring MVC框架介绍 Spring MVC&#xff08;Model-View-Controller&#xff09;是一个基于Java的开源框架&#x…

移动开发新风向?HarmonyOS、车载、音视频、flutter

目前&#xff0c;移动开发已经处于饱和的阶段&#xff0c;Android开发也不如当年盛况&#xff0c;已经不再像前几年前那么火爆。正如一种编程语言如果经历过盛极一时&#xff0c;那么必然有这样的一条曲线&#xff0c;像我们学的正弦曲线先急速上升&#xff0c;然后到达顶点&am…

拼图 游戏

运行出的游戏界面如下&#xff1a;按住A不松开&#xff0c;显示完整图片&#xff1b;松开A显示随机打乱的图片 User类 package domain;/*** ClassName: User* Author: Kox* Data: 2023/2/2* Sketch:*/ public class User {private String username;private String password;p…

服务器数据恢复—V7000存储raid5崩溃导致上层卷无法使用的数据恢复案例

服务器数据恢复环境&#xff1a; 某品牌V7000存储中有一组由几十块硬盘组建的raid5阵列。上层操作系统为windows server&#xff0c;NTFS分区。 服务器故障&#xff1a; 有一块硬盘出现故障离线&#xff0c;热备盘自动上线替换离线硬盘。在热备盘上线同步数据的过程&#xff0c…

WordPress 外链跳转插件

WordPress 外链跳转插件是本站开发的一款WordPress插件&#xff0c;能对文中外链添加一层过滤&#xff0c;有效防止追踪&#xff0c;以及提醒用户。 类似于知乎、CSDN打开其他链接的提示。 后台可以设置白名单 学习资料源代码&#xff1a;百度网盘 密码&#xff1a;123

低功耗蓝牙模块在运动健身追踪中的革新应用

随着健康和健身意识的不断提升&#xff0c;人们对运动追踪技术的需求日益增加。低功耗蓝牙模块的引入为运动健身追踪领域带来了新的可能性。本文将探讨低功耗蓝牙模块在运动健身追踪中的创新应用&#xff0c;重点关注其在设备连接性、数据传输和用户体验方面的优势。 随着科技的…

上海亚商投顾:沪指震荡下跌 成交量继续下破8000亿

上海亚商投顾前言&#xff1a;无惧大盘涨跌&#xff0c;解密龙虎榜资金&#xff0c;跟踪一线游资和机构资金动向&#xff0c;识别短期热点和强势个股。 一.市场情绪 沪指昨日震荡调整&#xff0c;深成指、创业板指午后跌超1%&#xff0c;北证50指数跌超7%&#xff0c;超百只北…

基于web宠颐生宠物医院系统设计与实现

基于web宠颐生医院系统开发与实现 摘要&#xff1a;时代飞速发展&#xff0c;网络也飞速发展&#xff0c;互联网许多的行业都可以用互联网实现了&#xff0c;互联网已经成为了人们生活中重要的一部分&#xff0c;或多或少的影响着我们的生活&#xff0c;互联网在给我带了方便的…

【洛谷算法题】P5716-月份天数【入门2分支结构】

&#x1f468;‍&#x1f4bb;博客主页&#xff1a;花无缺 欢迎 点赞&#x1f44d; 收藏⭐ 留言&#x1f4dd; 加关注✅! 本文由 花无缺 原创 收录于专栏 【洛谷算法题】 文章目录 【洛谷算法题】P5716-月份天数【入门2分支结构】&#x1f30f;题目描述&#x1f30f;输入格式&a…

安卓apk抓包

起因 手机&#xff08;模拟器&#xff09;有时候抓不到apk的包&#xff0c;需要借助Postern设置一个代理&#xff0c;把模拟器的流量代理到物理机的burp上。 解决方案 使用Postern代理&#xff0c;把apk的流量代理到burp。 Postern是一个用于代理和网络流量路由的工具&#xf…