Java8 Lambda总结

什么是Lambda?

Lambda是一个匿名函数,我们可以把Lambda表达式理解为是一段可以传递的代码(将代码像参数一样进行传递,称为行为参数化)。Lambda允许把函数作为一个方法的参数(函数作为参数传递进方法中),要做到这一点就需要了解,什么是函数式接口,这里先不做介绍,等下一篇在讲解。
首先先看一下lambda长什么样?
正常写法:

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

lambda写法:

new Thread(() -> System.out.println("hello lambda")
).start();

怎么样?是不是感觉很简洁,没错,这就是lambda的魅力,他可以让你写出来的代码更简单、更灵活。

Lambda怎么写?

大家看一些上面的这个图,这就是lambda的语法,一个lambda分为三部分:参数列表、操作符、lambda体。以下是lambda表达式的重要特征:

  • 可选类型声明
    不需要声明参数类型,编译器可以统一识别参数值。也就说(s) -> System.out.println(s)和 (String s) -> System.out.println(s)是一样的编译器会进行类型推断所以不需要添加参数类型。
  • 可选的参数圆括号
    一个参数无需定义圆括号,但多个参数需要定义圆括号。例如:s -> System.out.println(s) 一个参数不需要添加圆括号。
    (x, y) -> Integer.compare(y, x) 两个参数添加了圆括号,否则编译器报错。
  • 可选的大括号
    如果主体包含了一个语句,就不需要使用大括号。

不需要大括号.

 s -> System.out.println(s) 

需要大括号

(s) -> {if (s.equals("s")) {System.out.println(s);}};  
  • 可选的返回关键字
    如果主体只有一个表达式返回值则编译器会自动返回值,大括号需要指定明表达式返回了一个数值。
    Lambda体不加{ }就不用写return:
    Comparator com = (x, y) -> Integer.compare(y, x);
    复制代码Lambda体加上{ }就需要添加return:
  Comparator<Integer> com = (x, y) -> {int compare = Integer.compare(y, x);return compare;}; 

类型推断

上面我们看到了一个lambda表达式应该怎么写,但lambda中有一个重要特征是可选参数类型声明,就是说不用写参数的类型,那么为什么不用写呢?它是怎么知道的参数类型呢?这就涉及到类型推断了。
java8的泛型类型推断改进:

支持通过方法上下文推断泛型目标类型
支持在方法调用链路中,泛型类型推断传递到最后一个方法

List<Person> ps = ...
Stream<String> names = ps.stream().map(p -> p.getName());

在上面的代码中,ps的类型是List,所以ps.stream()的返回类型是Stream。map()方法接收一个类型为Function<T, R>的函数式接口,这里T的类型即是Stream元素的类型,也就是Person,而R的类型未知。由于在重载解析之后lambda表达式的目标类型仍然未知,我们就需要推导R的类型:通过对lambda表达式lambda进行类型检查,我们发现lambda体返回String,因此R的类型是String,因而map()返回Stream。绝大多数情况下编译器都能解析出正确的类型,但如果碰到无法解析的情况,我们则需要:

使用显式lambda表达式(为参数p提供显式类型)以提供额外的类型信息
把lambda表达式转型为Function<Person, String>
为泛型参数R提供一个实际类型。( map(p -> p.getName()))

方法引用

方法引用是用来直接访问类或者实例已经存在的方法或构造方法,提供了一种引用而不执行方法的方式。是一种更简洁更易懂的Lambda表达式,当Lambda表达式中只是执行一个方法调用时,直接使用方法引用的形式可读性更高一些。
方法引用使用 “ :: ” 操作符来表示,左边是类名或实例名,右边是方法名。
(注意:方法引用::右边的方法名是不需要加()的,例:User::getName)
方法引用的几种形式:

类 :: 静态方法
类 :: 实例方法
对象 :: 实例方法

例如:

Consumer<String> consumer = (s) -> System.out.println(s);

等同于:

Consumer<String> consumer = System.out::println;

例如:

 Function<String, Integer> stringToInteger = (String s) -> Integer.parseInt(s);

等同于:

 Function<String, Integer> stringToInteger = Integer::parseInt;

例如:

BiPredicate<List<String>, String> contains = (list, element) -> list.contains(element);

等同于:

   BiPredicate<List<String>, String> contains = List::contains;

注意:

Lambda体中调用方法的参数列表与返回值类型,要与函数式接口中抽象方法的函数列表和返回值类型保存一致
若Lambda参数列表中的第一个参数是实例方法的调用者,而第二个参数是实例方法的参数时,可以使用ClassName::method

构造器引用
语法格式:类名::new
例如:
Supplier supplier = ()->new User();

等同于:
Supplier supplier = User::new;
复制代码注意:
需要调用的构造器方法与函数式接口中抽象方法的参数列表保持一致。
Lambda是怎么实现的?
研究了半天Lambda怎么写,可是它的原理是什么?我们简单看个例子,看看真相到底是什么:

public class StreamTest {public static void main(String[] args) {printString("hello lambda", (String s) -> System.out.println(s));}public static void printString(String s, Print<String> print) {print.print(s);}
}
@FunctionalInterface
interface Print<T> {public void print(T t);
}

上面的代码自定义了一个函数式接口,定义一个静态方法然后用这个函数式接口来接收参数。编写完这个类以后,我们到终端界面javac进行编译,然后用javap(javap是jdk自带的反解析工具。它的作用就是根据class字节码文件,反解析出当前类对应的code区(汇编指令)、本地变量表、异常表和代码行偏移量映射表、常量池等等信息。)进行解析,如下图:

执行javap -p 命令 ( -p -private 显示所有类和成员)

看上图发现在编译Lambda表达式生成了一个lambda$main$0静态方法,这个静态方法实现了Lambda表达式的逻辑,现在我们知道原来Lambda表达式被编译成了一个静态方法,那么这个静态方式是怎么调用的呢?我们继续进行
执行javap -v -p 命令 ( -v -verbose 输出附加信息)

public com.lxs.stream.StreamTest();
descriptor: ()V
flags: ACC_PUBLIC
Code:
stack=1, locals=1, args_size=1
0: aload_0
1: invokespecial #1 // Method java/lang/Object.""😦)V
4: return
LineNumberTable:
line 7: 0

public static void main(java.lang.String[]);
descriptor: ([Ljava/lang/String;)V
flags: ACC_PUBLIC, ACC_STATIC
Code:
stack=2, locals=1, args_size=1
0: ldc #2 // String hello lambda
2: invokedynamic #3, 0 // InvokeDynamic #0:print:()Lcom/lxs/stream/Print;
7: invokestatic #4 // Method printString:(Ljava/lang/String;Lcom/lxs/stream/Print;)V
10: return
LineNumberTable:
line 10: 0
line 12: 10

public static void printString(java.lang.String, com.lxs.stream.Print<java.lang.String>);
descriptor: (Ljava/lang/String;Lcom/lxs/stream/Print;)V
flags: ACC_PUBLIC, ACC_STATIC
Code:
stack=2, locals=2, args_size=2
0: aload_1
1: aload_0
2: invokeinterface #5, 2 // InterfaceMethod com/lxs/stream/Print.print:(Ljava/lang/Object;)V
7: return
LineNumberTable:
line 15: 0
line 16: 7
Signature: #19 // (Ljava/lang/String;Lcom/lxs/stream/Print<Ljava/lang/String;>;)V

private static void lambda$mainKaTeX parse error: Expected 'EOF', got '#' at position 183: … getstatic #̲6 …Lookup of class java/lang/invoke/MethodHandles
BootstrapMethods:
0: #27 invokestatic java/lang/invoke/LambdaMetafactory.metafactory:(Ljava/lang/invoke/MethodHandlesKaTeX parse error: Expected 'EOF', got '#' at position 201: …guments: #̲28 (Ljava/lang/…mainKaTeX parse error: Expected 'EOF', got '#' at position 31: …tring;)V #̲30 (Ljava/lang/…Lookup of class java/lang/invoke/MethodHandles
BootstrapMethods:
0: #27 invokestatic java/lang/invoke/LambdaMetafactory.metafactory:(Ljava/lang/invoke/MethodHandlesKaTeX parse error: Expected 'EOF', got '#' at position 201: …guments: #̲28 (Ljava/lang/…mainKaTeX parse error: Expected 'EOF', got '#' at position 31: …tring;)V #̲30 (Ljava/lang/…main$0作为参数传了进去,我们来看metafactory 的方法里的实现代码:
public static CallSite metafactory(MethodHandles.Lookup caller,
String invokedName,
MethodType invokedType,
MethodType samMethodType,
MethodHandle implMethod,
MethodType instantiatedMethodType)
throws LambdaConversionException {
AbstractValidatingLambdaMetafactory mf;
mf = new InnerClassLambdaMetafactory(caller, invokedType,
invokedName, samMethodType,
implMethod, instantiatedMethodType,
false, EMPTY_CLASS_ARRAY, EMPTY_MT_ARRAY);
mf.validateMetafactoryArgs();
return mf.buildCallSite();
}
复制代码在buildCallSite的函数中,是函数spinInnerClass 构建了这个内部类。也就是生成了一个StreamTest?Lambda$1.class这样的内部类,这个类是在运行的时候构建的,并不会保存在磁盘中。
@Override
CallSite buildCallSite() throws LambdaConversionException {
final Class<?> innerClass = spinInnerClass();
以下省略。。。
}
复制代码如果想看到这个构建的类,可以通过设置环境参数
System.setProperty(“jdk.internal.lambda.dumpProxyClasses”, " . ");
会在你指定的路径 . 当前运行路径上生成这个内部类。我们看下一下生成的类长什么样

从图中可以看出动态生成的内部类实现了我自定义的函数式接口,并且重写了函数式接口中的方法。
我们在javap -v -p StreamTest?LambdaKaTeX parse error: Expected '}', got 'EOF' at end of input: …ream.StreamTest$Lambda$1();
descriptor: ()V
flags: ACC_PRIVATE
Code:
stack=1, locals=1, args_size=1
0: aload_0
1: invokespecial #10 // Method java/lang/Object.""😦)V
4: return

public void print(java.lang.Object);
descriptor: (Ljava/lang/Object;)V
flags: ACC_PUBLIC
Code:
stack=1, locals=2, args_size=2
0: aload_1
1: checkcast #15 // class java/lang/String
4: invokestatic #21 // Method com/lxs/stream/StreamTest.lambda$mainKaTeX parse error: Expected 'EOF', got '#' at position 84: …ions: 0: #̲13() } 复制代码发现在重…main0方法。总结:这样实现了Lambda表达式,使用invokedynamic指令,运行时调用LambdaMetafactory.metafactory动态的生成内部类,实现了函数式接口,并在重写函数式接口中的方法,在方法内调用lambda0方法。 总结: 这样实现了Lambda表达式,使用invokedynamic指令,运行时调用LambdaMetafactory.metafactory动态的生成内部类,实现了函数式接口,并在重写函数式接口中的方法,在方法内调用lambda0Lambda使invokedynamicLambdaMetafactory.metafactorylambdamain$0,内部类里的调用方法块并不是动态生成的,只是在原class里已经编译生成了一个静态的方法,内部类只需要调用该静态方法。

作者:俩右
链接:https://juejin.im/post/6844903890274484231
来源:掘金
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

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

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

相关文章

本地java【动态监听】zk集群节点变化

【README】搭建zk cluster&#xff0c; refer 2 https://blog.csdn.net/PacosonSWJTU/article/details/111404364 【1】 动态监听代码&#xff0c;需要写在 建立zk 连接的watcher 实现类里&#xff0c;如下&#xff1a; public class TestZK {/*** zk server 连接串 */privat…

架构师必须搞懂DNS,一篇文章就够了。

转载自 架构师必须搞懂DNS&#xff0c;一篇文章就够了。概念DNS&#xff0c;全称Domain Name System&#xff0c;即域名系统&#xff0c;搞清楚&#xff0c;它不是DNF地下城与勇士。DNS是怎么来的&#xff0c;我们知道要访问一个服务器的资源可以通过IP的形式访问&#xff0c;但…

Java 流式编程stream

目录什么是Stream&#xff1f;怎么创建Stream?Stream的中间操作Stream的终端操作什么是Stream&#xff1f; Stream它并不是一个容器&#xff0c;它只是对容器的功能进行了增强&#xff0c;添加了很多便利的操作,例如查找、过滤、分组、排序等一系列的操作。并且有串行、并行两…

本地java判断zk节点是否存在

【README】java 本地连接zk集群&#xff0c;https://blog.csdn.net/PacosonSWJTU/article/details/111404364 【1】判断节点是否存在 /*** 测试 zk * author pacoson**/ public class TestZK {/*** zk server 连接串 */private String connectString "192.168.163.20…

架构师必须掌握的各种编码:ASCII、ISO-8859-1、GB2312

转载自 架构师必须掌握的各种编码&#xff1a;ASCII、ISO-8859-1、GB2312编码在我们日常开发过程中经常有遇到&#xff0c;常见的编码格式有ASCII、ISO-8859-1、GB2312、GBK、GB18030、UNICODE、UTF-8、UTF-16等&#xff0c;其中GB2312、GBK、GB18030、UTF-8、UTF-16都可以用来…

vue 使用 el-image图片无法显示

问题&#xff1a;在vue 项目中使用 el-image 后&#xff0c;在图片路径无误的情况下显示图片加载失败 <el-carousel trigger"click" :height"carouselHeight"><el-image src"images/top1.jpg" fit"fill" style"height:…

java本地监听zk服务器节点【动态上下线】

【README】 java本地访问 zk cluster&#xff0c; refer 2 https://blog.csdn.net/PacosonSWJTU/article/details/111404364 【1】 客户端监听 zk节点变化 1&#xff09; zk客户端代码——http访问的客户端代码&#xff08;请求zk获取 http服务器的上下线信息&#xff09;…

面象对象设计6大原则之一:单一职责原则

转载自 面象对象设计6大原则之一&#xff1a;单一职责原则单一职责原则&#xff08;SRP&#xff09;&#xff0c;The Single Responsibility Principle定义一个类的修改只能有一个被修改的原因。通俗地讲&#xff0c;就是一个类只能负责一个职责&#xff0c;修改一个类不能影响…

vue 实现 js css html分离

方法一 分别创建 index.js, index.css index.js: export default {data:function(){return {//...};},methods:{//...} }index.vue 代码 <template>.... </template><script> //index.js 的相对路径 import index from "./index.js";// 名字可…

基于centos8搭建zookeeper集群

【README】 本文基于centos8 搭建 1&#xff0c;其他linux版本&#xff0c;命令可能不同&#xff1b; 2&#xff0c;集群包括3个节点&#xff0c;如下&#xff08;因为采用NAT模型进行网络连接&#xff0c;需要让windows和linux机器在同一个网段&#xff09;&#xff1a; ce…

面象对象设计6大原则之二:开放封闭原则

转载自 面象对象设计6大原则之二&#xff1a;开放封闭原则开放封闭原则&#xff08;OCP&#xff09;&#xff0c;The Open Closed Principle定义一个软件的实体&#xff0c;包括类、方法、模块、应该对扩展开放&#xff0c;对修改关闭。也就是说一个软件的实体应该通过扩展的方…

Java 泛型实现方法 — 擦拭法

所谓擦拭法是指&#xff0c;虚拟机对泛型其实一无所知&#xff0c;所有的工作都是编译器做的。 例如&#xff0c;我们编写了一个泛型类Pair&#xff0c;这是编译器看到的代码&#xff1a; public class Pair<T> {private T first;private T last;public Pair(T first, T…

shell脚本启动kafka集群的多台节点

【README】 由于集群有多台机器&#xff0c;启动kafka &#xff0c;查看其状态&#xff0c;都需要每台机器去执行命令&#xff0c; 可以写个脚本基于ssh免密登录批量执行&#xff1b; 【1】启动kafak (kk.sh) #!/bin/bash case $1 in "start"){for i in centos201…

面象对象设计6大原则之三:里氏替换原则

转载自 面象对象设计6大原则之三&#xff1a;里氏替换原则里氏替换原则&#xff08;LSP&#xff09;&#xff0c;The Liskov Substitution Principle定义所有引用基类的地方必须能透明地引用其子类的对象&#xff0c;即子类可以拓展父类的功能&#xff0c;但不能修改父类已有的…

kafka命令行操作

【README】 kafka集群 -- kafka集群 3台机器 centos201 192.168.163.201 centos202 192.168.163.202 centos203 192.168.163.203【1】主题topic 操作命令 序号 命令 1 kafka-topics.sh -- create 新增 2 kafka-topics.sh -- list 查看列表 3 kafka-topics.sh -- d…

面象对象设计6大原则之四:接口隔离原则

转载自 面象对象设计6大原则之四&#xff1a;接口隔离原则接口隔离原则&#xff08;ISP&#xff09;&#xff0c;The Interface Segregation Principle定义客户端不需要强迫依赖那些它们不需要的接口。类与接口的依赖应该建议在最小的接口上&#xff0c;也就是说接口应该最小化…

Spring 拦截器和过滤器中自动注入为 null 的原因及解决方案

起因 开发过程中在过滤器&#xff08;filter&#xff09;中注入Bean出现空指针异常&#xff0c;通过查找资料了解空指针的原因&#xff0c;特此记录。 问题分析 由于其他bean在service&#xff0c;controller层注入一点问题也没有&#xff0c;开始根本没意识到Bean无法注入是…

kafka命令行生产者消费者测试

【README】 基于命令行开启生产者&#xff0c;消费者线程&#xff0c;测试kafka的消费转发功能&#xff1b; 【1】生产者与消费者 生产者201 [rootcentos201 logs]# kafka-console-producer.sh --topic first --broker-list centos201:9092 >hello-world >sichuan-c…

面象对象设计6大原则之五:依赖倒置原则

转载自 面象对象设计6大原则之五&#xff1a;依赖倒置原则依赖倒置原则&#xff08;DIP&#xff09;&#xff0c;The Dependency Inversion Principle定义1、高层模块不应该依赖低层模块&#xff0c;两都应该依赖于抽象。2、抽象不依赖于具体细节。3、具体细节应该依赖于抽象。…

实现简单的注解型MVC框架 —— 低配SpringMVC

文章目录目标最终效果展示基本步骤1. 解析控制器类&#xff1a;2. 解析处理函数&#xff1a;3. 解析处理函数变量名&#xff1a;4. 监听TCP连接&#xff1a;5. 实现路由函数&#xff1a;知识点总结目标 与SpringMvc定义Controller类似效果 最终效果展示 主类 package org.e…