Java基础-lambda表达式

lambda表达式

    • 前言
    • 前置知识
      • λ小故事
      • 函数式编程起源: λ演算法
    • 概念
    • Lambda && 匿名类
      • 无参类型的简写
      • 带参函数的简写
    • 简写的依据
    • 自定义函数接口
    • lambda && 匿名类JVM层面区别
      • 匿名内部类实现
      • Lambda表达式实现
      • 推论,this引用的意义
    • lambda && 集合
      • Collection中的新方法
        • forEach()
        • removeIf()
        • replaceAll()
        • sort()
        • spliterator()
        • stream() && parrllelStream()
      • Map中的新方法
        • forEach()
        • getOrDefault()
        • putIfAbsent()
        • remove()
        • replace()
        • replaceAll()
        • merge()
        • compute()
        • computeIfAbsent()
        • computeIfPresent()

前言

函数式编程,曾经有过一段黄金时代,后来又因面向对象范式的崛起而逐步变为小众范式。但是,函数式编程目前又开始在不同的语言中流行起来了,像Java 8、JS、Rust等语言都有对函数式编程的支持。
面向对象编程是对数据进行抽象,而函数式编程是对行为进行抽象.
这种抽象方式好处还是有很多的,可以让程序员写出更容易阅读的代码,这种代码更清晰的表达了业务逻辑,而不是从机制上如何实现.
在写回调函数和事件处理器时,我们不用再纠缠于匿名内部类的冗繁和可读性.
核心思想: 使用不可变值和函数,函数对一个值进行处理,映射成另一个值

前置知识

λ小故事

这块有时间填坑

函数式编程起源: λ演算法

λ演算(读作lambda演算),它从数理逻辑(Mathematical logic)发展而来,使用变量绑定(binding)和代换规则(substitution)来研究函数如何抽象化定义(define),函数如何被应用(apply)以及递归(recursion)的形式系统.
在这里插入图片描述

λ演算式有三个要点:

  • 绑定关系 : 变量任意性,x,y,z都可以,它仅仅是具体数据的代称
  • 递归定义 : λ项递归定义,M可以是一个λ项
  • 替换归约 : λ项可应用,空格分隔表示对M应用N ,N可以是一个λ项

注意,在λ演算法里面,任何东西都是在函数之上建立起来的,而且λ里面的函数和我们编程中的函数几乎是一模一样的,举个例子来看:

λ函数如下:
λx.xy
对应的函数如下
def add(i,j):
return i+j

add括号里面的i和j就是形参
所以对应λ演算法的函数就是一个x,而它.后面就是函数体(定义了函数要做什么),在调用函数的时候,形参和函数体会被形参括起来,比如:(λx.xy)a == add(1,3) 其中这个a就是函数里的实参,如下图
请添加图片描述

我们习惯中的编程语言的函数在里面写的都是操作和指令,告诉计算机传进来的参数要对其进行什么操作.
也就是先有的数据和指令,函数是被定义在数据和指令这两个基础概念之上的
而对于λ演算法来说,函数是第一概念,最初是没有数据和指令的,也就是说数据和指令也需要建立在函数这个概念之上.

我们用上图的例子来说明
对于上图例子而言,x我们知道是实参,但是y是什么呢??
其实y是什么根本就不重要,因为在λ演算法里,重点不是符号是什么,而是两个符号之间的关系.
第一个x是形参,第二个y是什么无所谓,它只要和第一个符号不一样就行.
为了帮助我们更好的理解,我们拿过来一个例子来看符号之间的关系
在这里插入图片描述
上面的内容看不太明白也没关系,可以去看一下这个b站视频:【4. 用“λ演算法”去理解,为什么函数式编程会有更少的bug】https://www.bilibili.com/video/BV1d34y1v7xr?vd_source=e69b8b220e292b8e4922bf6622e13c51
受限于时间和篇幅,没办法把视频中的思想很好的表达出来,不妨直接去看一下这个视频,相信就可以更好理解了.

概念

lambda表达式,也称λ表达式,是一种匿名函数,即没有函数名的函数.
它是基于数学中的λ演算得名,直接对应其中的lambda抽象.
在编程语言中,lambda允许我们以简洁的方式表示函数,无需进行完整的函数定义.
具体来说,lambda表达式由参数和表达式组成,其中参数是函数的输入,表达式是函数的输出.
lambda表达式主要特点是其匿名性和简洁性,使得我们可以快速定义简单的函数,并在需要的地方使用它们.
此外,它可以作为参数传递给其他函数,或者从其他函数中返回,从而实现函数的灵活组合和调用.
lambda表达式不仅仅是匿名内部类的语法糖,JVM内部通过invokedynamic指令来实现lambda表达式的.

Lambda && 匿名类

lambda表达式可以简化匿名内部类的书写,但lambda表达式并不能取代所有的匿名内部类,只能用来取代函数接口(Functional Interface)的简写.

无参类型的简写

如果需要一个线程,常见的写法是:

new Thread( new Runnable(){@Overridepublic void run(){System.out.println("Thread run()");}
}).start();

Thread类传递了一个匿名的Runnable对象,重载Runnable接口的run()方法来实现相应逻辑,这是JDK7及其以前的做法,虽然省去了为类起名字的烦恼,但是还不够简化,在JDK8中可以简化如下方式:

new Thread(() ->    System.out.println("Thread run()"); //省略接口名和方法名
)

上述代码和匿名内部类作用是一样的,但比匿名内部类更进一步,连着接口名和函数名都一同省掉了.

带参函数的简写

如果给一个字符串列表通过自定义比较器,按照字符串长度进行排序,JDK7书写形式

List<String> list = Arrays.asList("I", "love", "you", "too");
Collections.sort(list,new Comparator<String>(){@Overridepublic int compare(String s1,String s2){if(s1==null)return -1;if(s2 == null)return 1;return s1.length() - s2.length();}});

上述代码通过内部类存储重载了Comparator接口的compare()方法,实现比较逻辑.
采用lambda表达式可简写如下:

// JDK8 Lambda表达式写法
List<String> list = Arrays.asList("I", "love", "you", "too");
Collections.sort(list,(s1,s2)->{if(s1 == null)return -1;if(s2 == null)return 1;return s1.length() - s2.length();{);

除了省略接口名和方法名,代码中把参数表的类型也忽略了.
得益于javac的类型推断机制,编译期能够根据上下文信息推断出参数的类型,当然也有推断失败的时候,这时候就需要手动指明参数类型了.
注意: Java是强类型语言,每个变量和对象都必需有明确的类型.

简写的依据

并不是所有的接口都可以使用lambda简写,能够使用lambda的依据是必须有相应的函数接口(函数接口,是指内部只有一个抽象方法的接口).这一点跟Java是强类型语言吻合,也就是说你并不能在代码的任何地方任性的写lambda表达式.
lambda的类型就是对应函数接口的类型.
lambda表达式另一个依据就是类型推断机制,在上下文信息足够多的情况下,编译期可以推断出参数表的类型,而不需要显示指名.lambda表达更多合法的书写形式如下:

// Lambda表达式的书写形式
Runnable run = () -> System.out.println("Hello World");// 1 :无参函数的简写
ActionListener listener = event -> System.out.println("button clicked");// 2 有参函数的简写
Runnable multiLine = () -> {// 3 代码块写法System.out.print("Hello");System.out.println(" Hoolee");
};
BinaryOperator<Long> add = (Long x, Long y) -> x + y;// 4 
BinaryOperator<Long> addImplicit = (x, y) -> x + y;// 5 类型推断机制

自定义函数接口

自定义函数接口很容易,只需要编写一个只有一个抽象方法的接口即可

@FunctionalInterface
public interface ConsumerInterface<T>{void accept(T t)
}

有了上述接口定义,我们可以写出下面的代码

ConsumerInterface<String> consumer = str -> System.out.println(str)

进阶一点的用法

class Mystream<T>{private List<T> list;public void myForEach(ConsumerInterface<T> consumer){for(T t:list){consumer.accept(t);}}
}
MyStream<String> stream = new MyStream<String>();
stream.myForEach(str -> System.out.println(str)); // 自定义函数接口书写lambda表达式

lambda && 匿名类JVM层面区别

lambda表达式似乎只是为了简化匿名内部类书写,看起来仅仅是通过语法糖在编译阶段把所有的lambda表达式替换成匿名内部类就可以了.
但其实并非如此,在JVM层面,lambda表达式和匿名内部类有着明显的差别.

匿名内部类实现

匿名内部类仍然是一个类,只是不需要程序员显示指定类名,编译期会自动为该类取名,编译之后会产生两个class文件:

public class MainAnonymousClass {public static void main(String[] args) {new Thread(new Runnable(){@Overridepublic void run(){System.out.println("Anonymous Class Thread run()");}}).start();;}
}

在这里插入图片描述
进一步分析主类MainAnonymousClass.class字节码,可发现其创建了匿名内部类的对象:

// javap -c MainAnonymousClass.class
public class MainAnonymousClass {...public static void main(java.lang.String[]);Code:0: new           #2                  // class java/lang/Thread3: dup4: new           #3                  // class MainAnonymousClass$1 /*创建内部类对象*/7: dup8: invokespecial #4                  // Method MainAnonymousClass$1."<init>":()V11: invokespecial #5                  // Method java/lang/Thread."<init>":(Ljava/lang/Runnable;)V14: invokevirtual #6                  // Method java/lang/Thread.start:()V17: return
}

Lambda表达式实现

Lambda表达式通过invokedynamic指令实现,书写lambda表达式不会产生新的类,如果有如下代码,编译之后只有一个class文件:

public class MainLambda {public static void main(String[] args) {new Thread(() -> System.out.println("Lambda Thread run()")).start();;}
}

编译后的结果:
在这里插入图片描述
通过javap反编译命名,我们可以看到Lambda表达式内部表示的不同:

// javap -c -p MainLambda.class
public class MainLambda {...public static void main(java.lang.String[]);Code:0: new           #2                  // class java/lang/Thread3: dup4: invokedynamic #3,  0              // InvokeDynamic #0:run:()Ljava/lang/Runnable; /*使用invokedynamic指令调用*/9: invokespecial #4                  // Method java/lang/Thread."<init>":(Ljava/lang/Runnable;)V12: invokevirtual #5                  // Method java/lang/Thread.start:()V15: returnprivate static void lambda$main$0();  /*Lambda表达式被封装成主类的私有方法*/Code:0: getstatic     #6                  // Field java/lang/System.out:Ljava/io/PrintStream;3: ldc           #7                  // String Lambda Thread run()5: invokevirtual #8                  // Method java/io/PrintStream.println:(Ljava/lang/String;)V8: return
}

反编译之后我们发现lambda表达式被封装了主类的一个私有方法,并通过invokedynamic指令进行调用

推论,this引用的意义

既然lambda表达式不是内部类的简写,那么lambda内部的this引用和内部类对象没关系了.
在Lambda表达式中this的意义跟在表达式外部完全一样。因此下列代码将输出两遍Hello Hoolee,而不是两个引用地址。

public class Hello {Runnable r1 = () -> { System.out.println(this); };Runnable r2 = () -> { System.out.println(toString()); };public static void main(String[] args) {new Hello().r1.run();new Hello().r2.run();}public String toString() { return "Hello Hoolee"; }
}

可能你会问,r1会输入Hello Hoolee呢?
我们来详细解释一下:

  • r1的lambda表达式中,this直接引用了Hello类实例,并调用了toString方法(当你尝试打印一个对象时,Java会自动调用该对象的toString方法)
  • 在 r2 的 Lambda 表达式中,你调用了 toString() 方法,没有显式使用 this,但由于 toString() 是 Hello 类的一个成员方法,它隐式地使用了 this 来调用该方法。

lambda && 集合

为引入lambda表达式,Java8新增了java.util.function包,里面包含常用函数接口,这是lambda表达式的基础,java集合框架也新增部分接口,以便与lambda表达式对接.
回顾一下Java集合框架的接口继承结构:
在这里插入图片描述
上图中绿色标注的接口类,表示在Java8中加入了新的接口方法,由于继承关系,他们相应的子类都会继承这些新方法,下面详细列举了这些方法.

接口名兼容的方法
CollectionforEach, stream, parallelStream, spliterator,removeIf
Listsort, replaceAll
MapforEach,getOrDefault,replaceAll,putIfAbsent,remove,replace, computeIfAbsent,computeIfPresent,compute,merge
这些新加入的方法大部分都要用到java.util.function包下的接口,意味着这些方法大部分都跟lambda表达式相关.
下面我们逐一学习这些方法.

Collection中的新方法

接口Collection和List新加入了一些方法,我们是以List的子类ArrayList为例来说明.

forEach()
  default void forEach(Consumer<? super T> action) {Objects.requireNonNull(action);for (T t : this) {action.accept(t);}}public interface Consumer<T> {/*** Performs this operation on the given argument.** @param t the input argument*/void accept(T t);//无关代码省略
}

方法签名为void forEach(Consumer<? super E> action),作用是对容器中的每个元素执行action指定的动作,其中Consumer是函数接口,里面只有一个待实现方法void accept(T t)(后面可以看到,这个方法叫什么根本不重要,甚至不需要记忆它的名字)

需求: 假设有一个字符串列表,需要打印出其中所有长度大于3的字符串
Java7及以前我们可以用增强for循环实现:

//使用增强for循环迭代
ArrayList<String> list = new ArrayList<>(Arrays.asList("I", "love", "you", "too"));
for(String str : list){if(str.length()>3)System.out.println(str)
}

使用forEach方法结合匿名内部类可以这样实现:

//使用forEach()结合匿名内部类迭代
ArrayList<String> list = new ArrayList<>(Arrays.asList("I", "love", "you", "too"));
list.forEach(new Consumer<String>(){@Overridepublic void accept(String str){if(str.length()>3)System.out.println(str)}
});

上面调用forEach且使用匿名内部类实现Consumer接口,目前为止没看到这种设计的好处,但我们还有Lambda表达式,如下

ArrayList<String> list = new ArrayList<>(Arrays.asList("I", "love", "you", "too"));
list.forEach( str -> {if(str.length() > 3)System.out.println(str);
});

我们不需要知道accept方法,也不需要知道Consumer接口,类型推导帮我们做了一切.

removeIf()
    default boolean removeIf(Predicate<? super E> filter) {Objects.requireNonNull(filter);boolean removed = false;final Iterator<E> each = iterator();while (each.hasNext()) {if (filter.test(each.next())) {each.remove();removed = true;}}return removed;}@FunctionalInterface
public interface Predicate<T> {/*** Evaluates this predicate on the given argument.** @param t the input argument* @return {@code true} if the input argument matches the predicate,* otherwise {@code false}*/boolean test(T t);//省略无关代码
}

方法签名boolean removeIf(Predicate <? super E> filter),作用是删除容器中所有满足filter指定条件的元素.
其中Predicate是一个函数接口,里面只有一个待实现的方法 boolean test(T t),同样这个方法的名字不重要,用的时候不需要写这个名字.

需求: 假设有一个字符串列表,需要删除其中所有长度大于3的字符串。

在迭代过程中对容器进行删除操作必须使用迭代器,否则会抛出ConcurrentModificationException,所有上述需求传统写法为:

// 使用迭代器删除列表元素
ArrayList<String> list = new ArrayList<>(Arrays.asList("I", "love", "you", "too"));
while(it.hasNext()){
if(it.next().length()>3) // 删除长度大于3的元素it.remove();
}

现在使用removeIf()方法结合匿名内部类,我们可以这样实现:

// 使用removeIf()结合匿名名内部类实现
ArrayList<String> list = new ArrayList<>(Arrays.asList("I", "love", "you", "too"));
list.removeIf(new Predicate<String>(){ //删除长度大于3的元素@Overridepublic boolean test(String str){return str.length() >3;}});

上述代码使用removeIf()方法,并使用匿名内部类实现Precicate接口.相信你已经可以使用lambda表达式写了

// 使用removeIf()结合Lambda表达式实现
ArrayList<String> list = new ArrayList<>(Arrays.asList("I", "love", "you", "too"));
list.removeIf(str -> str.length() >3); //删除长度大于3的元素

看的出来,使用lambda不需要记忆Predicate接口名,也不需要记忆test()方法名,只需要知道此处需要一个返回布尔类型的lambda表达式就可以了.

replaceAll()
  public void replaceAll(UnaryOperator<E> operator) {root.replaceAllRange(operator, offset, offset + size);
}
sort()
spliterator()
stream() && parrllelStream()

Map中的新方法

forEach()
getOrDefault()
putIfAbsent()
remove()
replace()
replaceAll()
merge()
compute()
computeIfAbsent()
computeIfPresent()

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

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

相关文章

代码随想录阅读笔记-字符串【右旋字符串】

题目 字符串的右旋转操作是把字符串尾部的若干个字符转移到字符串的前面。给定一个字符串 s 和一个正整数 k&#xff0c;请编写一个函数&#xff0c;将字符串中的后面 k 个字符移到字符串的前面&#xff0c;实现字符串的右旋转操作。 例如&#xff0c;对于输入字符串 "a…

【STL源码剖析】【2、空间配置器——allocator】

文章目录 1、什么是空间配置器&#xff1f;1.1设计一个简单的空间配置器&#xff0c;JJ::allocator 2、具备次配置力( sub-allocation)的 SGI 空间配置器2.1 什么是次配置力2.2 SGI标准的空间配置器&#xff0c;std::allocator2.2 SGI特殊的空间配置器&#xff0c;std::alloc2.…

录视频的软件推荐,助力视频内容创作

随着网络技术的发展和在线教育的兴起&#xff0c;录制视频教程的需求日益增加。无论是制作教学课程、分享办公技巧&#xff0c;还是录制游戏过程&#xff0c;一款好用的录屏软件都至关重要。本文将深入介绍三款录视频的软件&#xff0c;帮助读者了解它们的特点和操作步骤&#…

OSPF外部路由及外部路由引入过程

OSPF自治域&#xff08;同运行了OSPF协议的设备&#xff09;&#xff1b;O_ASE——代表OSPF的外部路由&#xff08;优先级150&#xff09;&#xff1b;1类LSA除了描述本身的直连状态、还描述本身的设备角色。 ASBR——自治系统边界路由器&#xff1b;同种路由协议也可做路由引…

谁能成为OpenAI的现实竞争对手吗?

文 | BFT机器人 前言&#xff1a; 自从与ChatGPT一起出现以来&#xff0c;OpenAI一直主导着人工智能市场&#xff0c;但它仍处于早期阶段。人工智能的历史可以追溯到几十年之前。但2022年11月ChatGPT的发布使生成式人工智能 (GenAI) 成为人们关注的焦点。从那时起&#xff0c;…

汽车研发项目管理数字化平台之阀门管理

阀门管理&#xff0c;在汽车研发流程中占据着举足轻重的地位&#xff0c;是确保项目精细化、系统化的关键所在。这一机制的核心在于通过设立“阀门”来控制和管理研发流程中的关键节点&#xff0c;从而确保项目在质量、进度和资源等方面均符合预期目标。在汽车行业高度竞争和持…

学习网络编程No.14【数据链路层ARP理解】

引言&#xff1a; 北京时间&#xff1a;2024/3/14/9:20&#xff0c;简单聊一聊我的日常。昨天晚上十点左右更新完新的一篇文章&#xff0c;回到宿舍简简单单花了个两首歌的时间洗了个澡&#xff0c;然后为了保持形象吹了个头发&#xff0c;哈哈哈&#xff01;当然对比以前的我…

Uniapp + SpringBoot 开发微信H5项目 微信公众号授权登录 JAVA后台(一、配置使用微信公众平台测试公众号)

申请测试号进行调试开发&#xff0c;测试号拥有大部分服务号有的接口权限。 一、接口配置信息填写校验 这里需要填写一个URL和一个Token验证字符串 我这里是用了natapp内网穿透 将本地的后台8080端口服务映射到了 http://x7zws8.natappfree.cc https://natapp.cn/在natapp官网…

Linux(Ubuntu)下安装paddleocr详细教程

PaddleOCR旨在打造一套丰富、领先、且实用的OCR工具库&#xff0c;助力开发者训练出更好的模型&#xff0c;并应用落地。 1、 Ubuntu安装教程&#xff1a; 首先安装paddlepaddle&#xff1a;pip install paddlepaddle -i https://mirror.baidu.com/pypi/simple 一般此项不会报错…

Redis的安装和部署教程(Windows环境)

一、安装Redis服务 1、下载Redis压缩包 以下这个是我网盘里面的&#xff08;这个是v8.0版本的&#xff0c;支持导入.rdb数据文件&#xff09; 链接&#xff1a;百度网盘 请输入提取码 提取码&#xff1a;x0f1 --来自百度网盘超级会员V5的分享 2、解压到文件夹 将下载的压缩…

【书生·浦语大模型实战营】学习笔记2

Lagent&#xff1a;智能体框架&#xff0c;实现将一个大语言模型转化为多种类型的智能体&#xff0c;更好地发挥InternLM的性能 浦语灵笔&#xff1a;视觉语言大模型 InternLM-Chat-7B智能对话Demo 环境准备 使用复制的internlm-demo环境 # 执行该脚本文件来安装项目实验环境…

Apple加速AI大跃进:最新发布的MM1 模型论文

每周跟踪AI热点新闻动向和震撼发展 想要探索生成式人工智能的前沿进展吗&#xff1f;订阅我们的简报&#xff0c;深入解析最新的技术突破、实际应用案例和未来的趋势。与全球数同行一同&#xff0c;从行业内部的深度分析和实用指南中受益。不要错过这个机会&#xff0c;成为AI领…

四川宏博蓬达法律咨询有限公司:专业引领,法律护航

在这个法治社会中&#xff0c;法律服务已成为企业和个人不可或缺的重要支持。四川宏博蓬达法律咨询有限公司凭借其专业的服务态度和丰富的法律知识&#xff0c;在法律服务领域独树一帜&#xff0c;赢得了社会各界的广泛认可。 一、公司背景实力雄厚 四川宏博蓬达法律咨询有限公…

电脑怎么快速重装系统win7

电脑重装系统是解决软件问题、提升系统性能的常用手段。随着技术发展,一键重装系统成为了许多用户的首选方法,因为它简化了繁琐的操作步骤,节省了大量时间。尤其是对于非技术人员来说,一键重装提供了一种快速高效且不易出错的系统安装方式。如果你需要快速重装win7,那么可…

2024最新阿里云幻兽帕鲁搭建服务器_Palworld联机多人游戏

玩转幻兽帕鲁服务器&#xff0c;阿里云推出新手0基础一键部署幻兽帕鲁服务器教程&#xff0c;傻瓜式一键部署&#xff0c;3分钟即可成功创建一台Palworld专属服务器&#xff0c;成本仅需26元&#xff0c;阿里云服务器网aliyunfuwuqi.com分享2024年新版基于阿里云搭建幻兽帕鲁服…

易基因:人类大脑的单细胞DNA甲基化和3D基因组结构|Science

大家好&#xff0c;这里是专注表观组学十余年&#xff0c;领跑多组学科研服务的易基因。 高通通量表观基因组分析技术可用于阐明大脑中细胞复杂性的基因调控程序。5-甲基胞嘧啶 (5mCs)是哺乳动物基因组中最常见的修饰碱基&#xff0c;大多数5mCs发生在胞嘧啶-鸟嘌呤二核苷酸&a…

git常见使用

1. 概念 分布式&#xff0c;有远程仓库和本地仓库的概念&#xff0c;因此要注意同步问题git是面向对象的&#xff0c;本质是内容寻址系统。.git目录下有个文件夹objects&#xff0c;存储git库中的对象&#xff0c;git就是根据object建立一种树形结构&#xff0c;将文件和通过h…

spring 没完没了

start 轻量级开源的j2ee框架&#xff0c;容器框架 装javabean aop ioc 定义一个starter的jar包&#xff0c;写一个configuration配置类&#xff0c;将bean定义其中&#xff0c;在starter包的meta-inf/spring.factories中写入配置类&#xff0c;springboot会按约定加载该配置类 …

用户留存【摘录】

留存&#xff0c;是一个产品或者说企业&#xff0c;能够持续存活和不断发展的立身之本。 例如企业级SaaS产品&#xff0c;通常采用按年为单位进行付费的订阅模式。如果产品没有留住用户的能力&#xff0c;用户便会在第二年取消订阅&#xff0c;这将使得企业之前的投入付之东流…