Java基础-lambda表达式

lambda表达式

    • 前言
    • 前置知识
      • λ小故事
      • 函数式编程起源: λ演算法
    • 概念
    • Lambda && 匿名类
      • 无参类型的简写
      • 带参函数的简写
    • 简写的依据
    • 自定义函数接口
    • lambda && 匿名类JVM层面区别
      • 匿名内部类实现
      • Lambda表达式实现
      • 推论,this引用的意义
    • lambda && 集合

前言

函数式编程,曾经有过一段黄金时代,后来又因面向对象范式的崛起而逐步变为小众范式。但是,函数式编程目前又开始在不同的语言中流行起来了,像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(){
  @Override
  public 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>(){
  @Override
  public 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(){
			@Override
			public 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/Thread
       3: dup
       4: new           #3                  // class MainAnonymousClass$1 /*创建内部类对象*/
       7: dup
       8: invokespecial #4                  // Method MainAnonymousClass$1."<init>":()V
      11: invokespecial #5                  // Method java/lang/Thread."<init>":(Ljava/lang/Runnable;)V
      14: invokevirtual #6                  // Method java/lang/Thread.start:()V
      17: 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/Thread
       3: dup
       4: invokedynamic #3,  0              // InvokeDynamic #0:run:()Ljava/lang/Runnable; /*使用invokedynamic指令调用*/
       9: invokespecial #4                  // Method java/lang/Thread."<init>":(Ljava/lang/Runnable;)V
      12: invokevirtual #5                  // Method java/lang/Thread.start:()V
      15: return

  private 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;)V
       8: 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 && 集合

早上起来填坑嘻嘻

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

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

相关文章

C++_day6:继承、多态

1.封装一个动物的基类&#xff0c;类中有私有成员&#xff1a;姓名&#xff0c;颜色&#xff0c;指针成员年纪 再封装一个狗这样类&#xff0c;共有继承于动物类&#xff0c;自己拓展的私有成员有&#xff1a;指针成员&#xff1a;腿的个数&#xff08;整型 int count&#xf…

arcgis 点连接到面(以地级市图层为例)

地级市图层进行“点到面”的连接&#xff0c;并输出 在点击地级市图层&#xff0c;右击——连接和关联——连接 选择基于空间位置的另一图层数据&#xff0c;文件选择上面输出并添加的图层文件&#xff0c;进行“点到面”的连接&#xff0c;可依据新需求选择平均值&#xff0c…

upload-labs-pass01

1.安装好环境进入关卡&#xff08;记得打开小皮&#xff09; 2.可以看到第一关是要求上传图片&#xff0c;但是同时限制了图片类型&#xff0c;那么如果我们将木马写入图片&#xff0c;但是类型又不在白名单&#xff0c;就要想办法绕过 3.可以看到这里的要求是有check&#xff…

1.中医学习-总论

目录 1.为什么要学中医 2.什么是中医 介绍 中医例子1&#xff1a; 中医例子2: 中医最高境界“大道至简” 中医讲究的是本质 中医核心&#xff1a;阴阳、表里、寒热、虚实 ​编辑医不叩门 3.阴阳 1.一天中的阴阳 2.一年中的阴阳 3.阴阳之间的关系 4.阴阳四季的变化 …

使用exe4j将java项目打包为exe文件(包含普通maven项目打jar包)

目录 背景实操普通maven项目打jar包手动构建修改pom文件 使用exe4j工具官网下载配置 注意事项&#xff08;锦囊&#xff09;jdk11没有jre文件夹生成独立的jre文件夹&#x1f302;经雨者&#xff0c;愿为人遮风挡雨&#xff08;jdk11直接生成的jre不行&#xff09;指定环境 过程…

PostgreSQL中vacuum 物理文件truncate发生的条件

与我联系&#xff1a; 微信公众号&#xff1a;数据库杂记 个人微信: iiihero 我是iihero. 也可以叫我Sean. iiheroCSDN(https://blog.csdn.net/iihero) Sean墨天轮 (https://www.modb.pro/u/16258) 数据库领域的资深爱好者一枚。 水木早期数据库论坛发起人 db2smth就是俺&am…

牛客题霸-SQL入门篇(刷题记录二)

本文基于前段时间学习总结的 MySQL 相关的查询语法&#xff0c;在牛客网找了相应的 MySQL 题目进行练习&#xff0c;以便加强对于 MySQL 查询语法的理解和应用。 以下内容是牛客题霸-SQL入门篇剩余的第 21-39 道题目的 SQL 代码答案。 由于涉及到的数据库表较多&#xff0c;因…

通过调整报文偏移解决CAN应用报文丢帧或周期过长问题

偏移原理 报文很多都是周期性发送的&#xff0c;但是如果每条报文都以一开始作为开始计时的时间点&#xff0c;也就是一开始就发送第一条报文&#xff0c;可能会导致CAN堵塞&#xff0c;导致丢帧或者某些报文某一时刻周期过长&#xff0c;就像下图这样&#xff0c;同一时刻CAN…

RVA和FOA转换---三

文章目录 修改初始值RVA和FOA转换RVAFOARVA和FOA的关系 本次内容包含如何修改程序中的初始值&#xff0c;和如何转换内存和文件的地址。 修改初始值 问题&#xff1a; 我们写了一个程序&#xff0c;可以输出一个结果&#xff0c;那么我们可以通过修改PE文件来改变这个输出结果…

数据结构与算法Bonus-KNN问题的代码求解过程

一、问题提出 &#xff08;一&#xff09;要求 1.随机生成>10万个三维点的点云&#xff0c;并以适当方式存储 2.自行实现一个KNN算法&#xff0c;对任意Query点&#xff0c;返回最邻近的K个点 3.不允许使用第三方库(e.g.flann&#xff0c;PCL,opencv)! 4.语言任选(推荐…

【JS逆向学习】猿人学第六题 js混淆 回溯

逆向目标 网址&#xff1a;https://match.yuanrenxue.cn/match/6接口&#xff1a;https://match.yuanrenxue.cn/api/match/6参数&#xff1a;payload(m、q) 逆向过程 老规矩&#xff0c;先来分析网络请求&#xff0c;加密的地方一目了然&#xff0c;没什么可多说的&#xff…

数目之差

解法一&#xff1a; 显然只需让多的在限度内最多即可 #include<iostream> #include<algorithm> using namespace std; #define endl \n void solve() {int n, k, num0 0, num1 0;cin >> n >> k;string s;cin >> s;for (int i 0; i < s.s…

【Paper Reading】6.RLHF-V 提出用RLHF的1.4k的数据微调显著降低MLLM的虚幻问题

分类 内容 论文题目 RLHF-V: Towards Trustworthy MLLMs via Behavior Alignment from Fine-grained Correctional Human Feedback 作者 作者团队&#xff1a;由来自清华大学和新加坡国立大学的研究者组成&#xff0c;包括Tianyu Yu, Yuan Yao, Haoye Zhang, Taiwen He, Y…

upload-labs 0.1 靶机详解

下载地址https://github.com/c0ny1/upload-labs/releases Pass-01 他让我们上传一张图片&#xff0c;我们先尝试上传一个php文件 发现他只允许上传图片格式的文件&#xff0c;我们来看看源码 我们可以看到它使用js来限制我们可以上传的内容 但是我们的浏览器是可以关闭js功能的…

【Spring MVC】Spring MVC拦截器(Interceptor)

目录 一、拦截器介绍 二、拦截器 Interceptor 定义 2.1 HandlerInterceptor接口 2.2 Spring MVC中提供的一些HandlerInterceptor接口实现类 1、AsyncHandlerInterceptor 2、WebRequestInterceptor 3、MappedInterceptor 4、ConversionServiceExposingInterceptor 三、拦…

《我的AUTOSAR之路》ECUM(二) 唤醒处理

ECUM唤醒 1 EcuM 唤醒源2 EcuM 唤醒源配置3 Can 通道唤醒源调用解析1 EcuM 唤醒源 AUTOSAR 唤醒过程包含的步骤 检查唤醒源和上报唤醒时间唤醒源保护唤醒过程是独立于 EcuM 休眠阶段的,但是唤醒时间可以用于休眠阶段 在整个 Ecu 所有阶段,唤醒事件都可以存在唤醒不单单指 Ecu …

【Java】高级篇1:异常处理

异常&#xff1a;程序在执行过程中出现的非正常情况&#xff0c;如果不处理最终会导致JVM的非正常停止。 Java的异常抛出机制 Java异常体系 1、Throwable 2、Error和Exception 异常处理方式 1、try-catch-finally&#xff08;捕获异常&#xff09; 基本结构&#xff1a; 使用…

小迪安全42WEB攻防-通用漏洞文件包含LFIRFI伪协议

#知识点: 1、解释什么是文件包含 2、分类-本地LFI&远程RFI 3、利用-配合上传&日志&会话 4、利用-伪协议&编码&算法等 #核心知识: 1、本地包含LFI&远程包含RF1-区别 一个只能包含本地&#xff0c;一个可以远程加载 具体形成原因由代码和环境配置文件决定…

机器学习----特征缩放

目录 一、什么是特征缩放&#xff1a; 二、为什么要进行特征缩放&#xff1f; 三、如何进行特征缩放&#xff1a; 1、归一化&#xff1a; 2、均值归一化&#xff1a; 3、标准化&#xff08;数据需要符合正态分布&#xff09;&#xff1a; 一、什么是特征缩放&#xff1a; 通…

Mysql增删改查(详解)

1.新增 insert into 表名 values 新增字段 。 如图&#xff1a; 这里我一共添加了三条数据。 2.查询 2.1 全列查询 select * from 表名 。 如图&#xff1a; 这里的全列查询可以展示一个表中的全部的数据。 2.2 指定查询 select 要查询的字段名 from 表名 。 比如…
最新文章