Lambda表达式方法引用举例_lambda函数引用-程序员宅基地

技术标签: java  Lambda表达式  开发语言  


Lambda表达式引入

假设我们现在有一个遍历集合List的需求
a.首先我们通过Lambda表达式的写法实现:
代码如下(示例):

public class MyLambdaTest {
    
    public static void main(String[] args) {
    
        List<String> stringList = Arrays.asList("肌肉猿","肌肉猿爱编程","程序员非晚");
        stringList.forEach(s -> {
    
            System.out.println(s);
        });
    }

程序运行结果:
在这里插入图片描述
b.我们通过匿名内部类的形式实现

public class MyLambdaTest2 {
    
    public static void main(String[] args) {
    
        List<String> stringList = Arrays.asList("肌肉猿","肌肉猿爱编程","程序员非晚");
        // 通过匿名内部类的形式替代Lambda表达式
        stringList.forEach(new Consumer<String>() {
    
            @Override
            public void accept(String s) {
    
                System.out.println(s);
            }
        });
    }
}

程序运行结果:
在这里插入图片描述
上述代码分析:
foreach()方法是Iterable接口的一个默认方法,在下面的方法的参数列表中我们可以知道,该方法需要一个Consumer类型的参数,方法体的内容则是一个for循环,进行对每一个对象的便利,最终处理方法则是调用accept()方法。

default void forEach(Consumer<? super T> action) {
    
        Objects.requireNonNull(action);
        for (T t : this) {
    
            action.accept(t);
        }
    }

当我们继续查看Consumer的accept(T)方法,我们不难得出Consumer是一个函数式接口(该接口的详细讲解见我专栏里的文章有详解)。

@FunctionalInterface
public interface Consumer<T> {
    

    /**
     * Performs this operation on the given argument.
     *
     * @param t the input argument
     */
    void accept(T t);

    /**
     * Returns a composed {@code Consumer} that performs, in sequence, this
     * operation followed by the {@code after} operation. If performing either
     * operation throws an exception, it is relayed to the caller of the
     * composed operation.  If performing this operation throws an exception,
     * the {@code after} operation will not be performed.
     *
     * @param after the operation to perform after this operation
     * @return a composed {@code Consumer} that performs in sequence this
     * operation followed by the {@code after} operation
     * @throws NullPointerException if {@code after} is null
     */
    default Consumer<T> andThen(Consumer<? super T> after) {
    
        Objects.requireNonNull(after);
        return (T t) -> {
     accept(t); after.accept(t); };
    }
}

此时此刻,我们通过对比上述的两种实现遍历集合List方式不难得出,stringList.forEach(s -> {System.out.println(s);})。
Lambda表达式s -> {System.out.println(s);}其实本质上就是实现了Consumer接口的一个匿名(内部类)对象。
大括号里的内容(System.out.println(s))相当于重写了accept()方法。
具体内部的底层实现细节见我专栏的系列文章


一、Lambda表达式的方法引用

经过目前的分析,使用lambda表达式就是传递进去的代码就是一种解决方案,拿什么参数,做什么操作,但是在使用的时候要注意冗余的问题出现。

a.冗余Lambda场景实例

首先我们编写简单接口来应用Lambda表达式

@FunctionalInterface
public interface t1 {
    
    void print(String str);
}

接下来我们用Lambda实现上述接口中的print打印方法

public class UseT1Test1 {
    
    private static void printString(t1 data){
    
        data.print("肌肉猿爱写Java");
    }

    public static void main(String[] args) {
    
        printString(str -> System.out.println(str));
    }
}

分析上述代码:
printString方法是为了调用接口t1中的print方法,不用考虑接口中的方法的具体实现逻辑以及输出方式。在main方法中通过Lambda表达式指定了函数式接口t1的具体操作方式--------拿到String类型并在控制台中输出。经过分析我们发现,对于字符串在控制台中的输出方案在类的重载中获得了明确的实现,则可以省略不用手动调用。
改进后的代码形式

public class UseT1Test2 {
    
    private static void printString(t1 data){
    
        data.print("肌肉猿爱写Java");
    }

    public static void main(String[] args) {
    
        printString(System.out::println);
    }
}

b.方法引用符(::)详解

定义:上述的简洁lambda表达式双冒号::称为引用运算符,其所在表达式称之为方法引用
应用场景:加入Lambda表达式要表达的函数方案已经存在在某个方法的实现中,则可以通过双冒号引用该方法作为Lambda的替代者。

Ⅰ.关于语义运算符的语义分析

上述代码中,System.out对象方法重载了print(String)方法,则对于接口t1中的函数式接口参数以下两者方法完全等效。

  • Lambda表达式写法: s -> System.out.println(s);
  • 方法引用写法: System.out::println
    第一种是拿到参数后通过Lambda之手,传递给输出语句进行打印输出。
    ps:Lambda表达式传递的参数一定是接口方法中所书写的抽象方法可以接收的参数类型,不然会报错
    第二种则是直接让System.out 中的printlnl来取代Lambda表达式,总之第二种方式复用了已有的方案更加简洁。

二、Lambda表达式各种方法引用

1.通过对象名引用成员变量

接口代码如下(示例):

@FunctionalInterface
public interface Printable {
    
    void print(String str);
}

当一个类中已经存在一个成员方法

public class MethodRefObject {
    
    public void printUpperCase(String str){
    
        System.out.println(str.toUpperCase());
    }
}

当我们需要使用类中printUpperCase成员方法来替代函数式接口的lambda,可以通过对象的实例来引用成员方法实现。

public class DemoMethodRef {
    
    private static void printString(Printable lambda){
    
        lambda.print("程序员非晚爱编程");
    }

    public static void main(String[] args) {
    
        MethodRefObject demoMethodRef = new MethodRefObject();
        printString(demoMethodRef::printUpperCase);
    }
}

2.通过类名称引用静态方法

首先还是定义一个函数式接口

@FunctionalInterface
public interface Calcable {
    
    int cal(int num);
}

使用Lambda表达式的写法示例

public class Demo01Lambda {
    
    public static void method(int num,Calcable lambda){
    
        System.out.println(lambda.cal(num));
    }

    public static void main(String[] args) {
    
        method(-1314,n->Math.abs(n));
    }
}

进阶写法就是使用方法引用

public class Demo02Lambda {
    
    public static void method(int num,Calcable lambda){
    
        System.out.println(lambda.cal(num));
    }

    public static void main(String[] args) {
    
        method(-1314,Math::abs);
    }
}

总结:在Java.lang.Math 中已经存在abs的静态方法

public static double abs(double a) {
    
        return (a <= 0.0D) ? 0.0D - a : a;
    }

上述的两种方式实际上是等效的

3.通过super引用成员方法

当类与类之间存在继承关系时,当Lambda表达式中出现super调用时,也可以使用方法引用进行替代。
首先定义函数式接口:

@FunctionalInterface
public interface Greetable {
    
    void greet();
}

然后定义父类中的内容

public class Human {
    
    public void sayHello(){
    
        System.out.println("hello,你好");
    }
}

最后定义子类Man中的内容,使用lambda表达式写法

public class Man extends Human{
    
    @Override
    public void sayHello() {
    
        System.out.println("大家好,我是Man");
    }
    
    // 定义方法Method,参数传递Greetable接口
    public void method(Greetable greetable){
    
        greetable.greet();
    }
    
    public void show(){
    
        // 调用method方法,使用lambda表达式 
        method(() -> new Human().sayHello());
        // 简化lambda表达式 直接使用super关键字替代父类对象
        method(() -> super.sayHello());
    }
    
}

另一种写法是直接使用方法引用来调用父类中的sayHello方法

public class RealMan extends Human{
    
    @Override
    public void sayHello() {
    
        System.out.println("大家好,我是真男人");
    }
    
    // 定义方法实现接口中的方法
    public void method(Greetable greetable){
    
        greetable.greet();
    }
    
    public void show(){
    
        method(super :: sayHello);
    }
}

4.通过this引用成员方法

声明this代表的就是当前的对象
首先定义函数式接口

@FunctionalInterface
public interface Human {
    
    void buy();
}

定义一个类来调用接口中的方法

public class RealHuman {
    
    private void marry(Human human){
    
        human.buy();
    }
    
    public void tobebetterman(){
    
        marry(()-> System.out.println("真男人要买套房"));
    }
}

上述中成为更好男人方法调用了结婚方法,因为后者的参数为函数式接口,可以使用lambda表达式,当表达式的内容在本类中已经存在,可以使用this关键字替代

public class RealHuman {
    
    private void buyHouse(){
    
        System.out.println("真男人要买房");
    }
    private void marry(Human human){
    
        human.buy();
    }
    public void tobebetterman(){
    
        marry(()->this.buyHouse());
    }
}

使用方法引用则更加简单,还不用写方法后边的括号了,示例如下

public class RealHuman {
    
    private void buyHouse(){
    
        System.out.println("真男人要买房");
    }
    private void marry(Human human){
    
        human.buy();
    }
    public void tobebetterman(){
    
        marry(this::buyHouse);
    }
}

5.通过类的构造器引用

根据定义构造器的名称和类名完全一样,所以构造器引用可以使用类名称::new格式表示
首先定义一个类

public class Human {
    
    private String name;

    public Human(String name) {
    
        this.name = name;
    }

    public String getName() {
    
        return name;
    }

    public void setName(String name) {
    
        this.name = name;
    }
}

然后定义函数式接口

@FunctionalInterface
public interface Human {
    
   RealHuman buildHuman(String name);
}

使用上述的函数式接口有两种方式,首先展示lambda表达式方式

public class LambdaHuman {
    
    public static void printName(String name,Human builder){
    
        System.out.println(builder.buildHuman(name).getName());
    }

    public static void main(String[] args) {
    
        printName("肌肉猿是真男人",name -> new RealHuman(name));
    }
}

另一种更加简洁的写法

public class RealHuman2 {
    
    public static void printName(String name,Human builder){
    
        System.out.println(builder.buildHuman(name).getName());
    }

    public static void main(String[] args) {
    
        printName("肌肉猿是真男人",RealHuman::new);
    }
}

此处的name -> new RealHuman(name)等价于RealHuman::new

6.数组构造器引用

声明:数组也是Object的子类,所以同样具有构造器,只是语法稍有不同
定义一个函数式接口

@FunctionalInterface
public interface ArrayBuilder {
    
    int[] buildArray(int length);
}

使用lambda表达式应用接口

public class DemoArrayInitRef {
    
    private static int[] initArray(int length,ArrayBuilder builder){
    
        return builder.buildArray(length);
    }

    public static void main(String[] args) {
    
        int[] array = initArray(10,length -> new int[length]);
    }
}

使用数组的构造器引用实现

public class DemoArrayInitRef2 {
    
    private static int[] initArray(int length,ArrayBuilder builder){
    
        return builder.buildArray(length);
    }

    public static void main(String[] args) {
    
        int[] array = initArray(10,int[]::new);
    }
}
版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/weixin_50503886/article/details/129018281

智能推荐

体积雾(dx9)-程序员宅基地

文章浏览阅读1.3k次。1.什么是体积雾? 这个问题通过图片来解答再合适不过了,下面是本文利用体积雾做的一个结果 所谓体积雾:顾名思义就是被限制了形状的雾,本文表述如何通过ImageProcess(图象处理)的方式实现体积雾。 2.常规雾原理 雾效最终体现在雾颜色与场景色的混合上。决定雾的浓度的关键就在这个混合因子上_体积雾

网站后台 服务器,网站后台数据 服务器-程序员宅基地

文章浏览阅读584次。网站后台数据 服务器 内容精选换一换华为云云市场搭建了包括基础软件市场、企业应用市场、建站市场、安全市场、服务市场、解决方案市场、人工智能市场、物联网市场8大子市场,种类丰富,产品众多,用户可以在华为云云市场找到适合自己业务的软件/服务。本文主要介绍了如何使用弹性云服务器的Linux实例搭建Magento电子商务系统。Magento是一款开源电子商务系统,设计灵活,具有模块化架构体系和丰富的功能,..._网站后台数据

详解< meta >标签常用的属性及方法_常用meta设置-程序员宅基地

文章浏览阅读1.4k次,点赞51次,收藏52次。`meta`标签在HTML中扮演着至关重要的角色,它能帮助开发者更好地控制网页的呈现方式、提升SEO效果、指导浏览器行为以及其他与网页相关的元数据管理。_常用meta设置

RabbitMq初识_guest user from anywhere on the network-程序员宅基地

文章浏览阅读145次。文章目录一、认识二、安装1.docker安装2. 配置文件2.RabbitMQ的简单指令三、分类1. Direct直连问题:2. Work queue3. Fanout4. Routing4.1 Direct(订阅)4.2 Topic四、SpringBoot整合RabbitMQ一、认识[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-TJhBYPKI-1586753..._guest user from anywhere on the network

今天端午节了,哈哈。。。-程序员宅基地

文章浏览阅读425次。今天过端午了,记录一下。。。先从百度入手,百度一篇《【创意Logo】浓浓的端午味儿……》来叙解了一下她的logo。端一盘粽子,呈一枝艾叶,与你一起过——端午。 自多了端午节假日,端午的“节”气立时浓了起来。这也让现时代的人们,体味到了旧时中国的传统意味。农历五月初五,端午。在中国,流传至今的节日“四大名旦”,当属春节、中秋、清明和端午。而这四者中,端午名气相..._csdn 今天端午了哈哈

JS 判断字符串中是否包含中文-程序员宅基地

文章浏览阅读974次,点赞3次,收藏2次。第一种方法: <script language="javascript"> function funcChina(){ var obj = document.form1.txtName.value; if(/.*[\u4e00-\u9fa5]+.*$/.test(obj))..._js 判断中文

随便推点

关于fastjson 对象转json出现$ref_"fastjson 转的json,\"$ref\":\"$. jsjson.parse("-程序员宅基地

文章浏览阅读781次。 现在微服务、前后分离什么的那么流行,多数新的程序在返数据给前端的时候直接返回json 数据,这样json工具就有用武之地了常用的json工具有 org.json、Gson 、albaba的fastjson。 昨天本来是用fastjson 对象转json了,发现有些重复引用的对象 会如下展示{"$ref":"$.data.list[0].list[34]"} 看着就像是坐标,这..._"fastjson 转的json,\"$ref\":\"$. jsjson.parse("

禾川Q1系列PLC官方教程_禾川学院培训资料-程序员宅基地

文章浏览阅读3.2k次。禾川Q系列PAC教程_禾川学院培训资料

Eclipse详细安装教程_eclipse的安装步骤-程序员宅基地

文章浏览阅读9.3k次,点赞7次,收藏26次。Eclipse安装教程前言一、Eclipse是什么?二、安装步骤1. Eclipse下载下载网址2. Eclipse安装前言Eclipse 是一个开放源代码的、基于Java的可扩展开发平台。一、Eclipse是什么?Eclipse 是一个开放源代码的、基于Java的可扩展开发平台。就其本身而言,它只是一个框架和一组服务,用于通过插件组件构建开发环境。幸运的是,Eclipse 附带了一个标准的插件集,包括Java开发工具(Java Development Kit,JDK)。二、安装步骤1. E_eclipse的安装步骤

计算机组成原理练习题_在底数取16及尾数为二进制的浮点数中,为了保持数值不变,阶码加1,尾数要( )。-程序员宅基地

文章浏览阅读497次,点赞20次,收藏21次。4. (单选题, 7分)计算机问世至今,新型机器不断推陈出新,不管怎样更新,依然保有“存储程序”的概念,最早提出这种概念的是( )。3. (单选题, 5分)在底数取 16及尾数为二进制的浮点数中 , 为了保持数值不变 , 阶码加 1, 尾数小数点要_( )__。2. (单选题, 5分)若浮点数的机器表示中,尾数用补码表示,则判断该浮点数是否为规格化的方法是尾数的最高数值位__( )__。5. (单选题, 5分)x=+0. 1011, y=+0. 0110,用补码运算得到[x-y]补 =___( )__。_在底数取16及尾数为二进制的浮点数中,为了保持数值不变,阶码加1,尾数要( )。

2013年东北c语言考试题答案,2013东北大学c语言试题-程序员宅基地

文章浏览阅读104次。信息学院 2013-2014 学年第 1 学期 程序设计基础 试题 a总分 班 级装一二 30三 24四 26五六七八九十(10) 若有说明语句:int *p,a;则能通过 scanf 语句正确给输入项读入数据 的程序段是_______。 A) *p=&a; scanf(“%d”,p); B) *p=&a; scanf(“%d”,*p); C) p=&a; scanf(“%..._c语言中的数据的类型不同,在内存中占据不同长度的存储单元

Meta 的 LLaMa 2 许可证并非开源许可证-程序员宅基地

文章浏览阅读347次。作者有幸受邀参加 Linux 基金会 7 月 27 日在瑞士日内瓦举办的 Open Source Congress,议程如下:https://oscongress2023.sched.com/。我看到了互动讨论环节建议参会者预先阅读的一些报告和文章里,其中有一篇 OSI 的博客《Meta 的 LLaMa 2 许可证不是开源许可证》,特别引起了我的注意,也因此取得了 OSI 的同意,将它翻译出来..._2023年7月20日,开源组织osi(open source initiative)发文指出,llama 2所适用的许

推荐文章

热门文章

相关标签