Java lambda表达式基本使用
代码示例:
java.lambda.LambdaExpression
1 本质
lambda表达式本质上是对匿名内部类实例的一种简化写法。
1.1 案例
有以下List<Integer>对象:
List<Integer> list = Arrays.asList(1, 3, 5, 7, 9, 2, 4, 6, 8, 10);
在对List进行从小大大排序时,会用到List#sort(Comparator)方法,需要传递实现Comparator接口的对象作为参数:
default void sort(Comparator<? super E> c) {
// 省略方法体
}
可以想到有如下四种不同的代码编写的方式。
1、 创建Comparator的实现类
根据需求,手动实现Comparator接口:
public class AscComparator implements Comparator<Integer> {
@Override
public int compare(Integer o1, Integer o2) {
return o1.compareTo(o2);
}
}
然后,创建AscComparator实例,传给List#sort(Comparator)方法:
Comparator<Integer> ascComparator = new AscComparator();
list.sort(ascComparator);
2、创建Comparator的匿名对象
可以直接创建Comparator的匿名对象,然后传给List#sort(Comparator)方法:
Comparator<Integer> anonymousComparator = new Comparator<Integer>() {
@Override
public int compare(Integer o1, Integer o2) {
return o1.compareTo(o2);
}
};
list.sort(anonymousComparator);
等价于:
list.sort(new Comparator<Integer>() {
@Override
public int compare(Integer o1, Integer o2) {
return o1.compareTo(o2);
}
});
3、lambda表达式
直接使用lambda表达式:
list.sort((o1, o2) -> o1.compareTo(o2));
4、方法引用
使用方法引用(方法引用具体概念和使用可以查看相关文章):
list.sort(Integer::compare);
可以明显看出,使用lambda表达式和方法引用极大提高了开发的速度,提升了代码的简洁性。
1.2 本质
实际上,lambda表达式只是JVM提供的语法糖。在JVM执行过程中,会根据lambda表达式的规则,动态创建出匿名的接口实现类对象。
lambda表达式本质上是Java对象。
可以通过查看lambda表达式的Class对象和实例对象来证明这一点:
public class LambdaExpression {
public void printConsumer(Consumer consumer) {
System.out.println(consumer.getClass());
System.out.println(consumer.getClass().getInterfaces()[0]);
System.out.println(consumer);
}
}
1、案例1
运行以下代码:
LambdaExpression lambdaObjPrinter = new LambdaExpression();
lambdaObjPrinter.printConsumer(o -> o.getClass());
lambdaObjPrinter.printConsumer(o -> o.getClass());
会有如下输出:
class lambda.LambdaExpression$$Lambda$1/2003749087
interface java.util.function.Consumer
lambda.LambdaExpression$$Lambda$1/2003749087@41629346
class lambda.LambdaExpression$$Lambda$2/1078694789
interface java.util.function.Consumer
lambda.LambdaExpression$$Lambda$2/1078694789@6d311334
- 这证明了执行过程中会根据
lambda表达式动态生成函数式接口的实现类,并创建该实现类的实例。 - 同时,先后执行的2个
lambda表达式,尽管格式相同,仍然动态生成了2个实现类。
查看编译后的.class文件如下:
LambdaExpression lambdaObjPrinter = new LambdaExpression();
lambdaObjPrinter.printConsumer((o) -> {
o.getClass();
});
lambdaObjPrinter.printConsumer((o) -> {
o.getClass();
});
2、案例2
运行如下代码:
LambdaExpression lambdaObjPrinter = new LambdaExpression();
for (int i = 0; i < 2; i++) {
lambdaObjPrinter.printConsumer(o -> o.getClass());
}
System.out.println("=============");
for (int i = 0; i < 2; i++) {
lambdaObjPrinter.printConsumer(o -> o.getClass());
}
会发现有如下输出:
class lambda.LambdaExpression$$Lambda$1/2003749087
interface java.util.function.Consumer
lambda.LambdaExpression$$Lambda$1/2003749087@41629346
class lambda.LambdaExpression$$Lambda$1/2003749087
interface java.util.function.Consumer
lambda.LambdaExpression$$Lambda$1/2003749087@41629346
=============
class lambda.LambdaExpression$$Lambda$2/1078694789
interface java.util.function.Consumer
lambda.LambdaExpression$$Lambda$2/1078694789@6d311334
class lambda.LambdaExpression$$Lambda$2/1078694789
interface java.util.function.Consumer
lambda.LambdaExpression$$Lambda$2/1078694789@6d311334
- 说明在不同
for循环中(while等循环结果相同),只会动态生成1个实现类。
查看编译后的.class文件如下:
LambdaExpression lambdaObjPrinter = new LambdaExpression();
int i;
for(i = 0; i < 2; ++i) {
lambdaObjPrinter.printConsumer((o) -> {
o.getClass();
});
}
System.out.println("=============");
for(i = 0; i < 2; ++i) {
lambdaObjPrinter.printConsumer((o) -> {
o.getClass();
});
}
- 说明这不是编译器编译的结果,应该是JVM执行时对循环语句中
lambda表达式的优化。
2 基本语法
lambda表达式本质上是对函数式接口的匿名实现类实例的一种简化写法:方法格式和lambda表达式格式一一对应。
对于执行逻辑而言,方法主要由两部分组成(没有返回值):形参和方法体。
lambda表达式与之对应:
1、形参:(t1, t2[, ……]),对应方法的形参(T1 t1, T2 t2[, ……])
2、箭头:->,固定
3、方法体:{},对应方法的方法体
2.1 分类
根据方法形参和返回值的不同组合,lambda表达式可以分成以下几类:
- 没有形参:
() -> {
// 方法体
}
- 一个形参:
(t) -> {
// 方法体
}
多个形参:
(t1, t2[, ……]) -> {
// 方法体
}没有返回值:
() -> {
// 方法体
}
- 有返回值:
() -> {
// 方法体
return something;
}
根据形参个数的不同,形参部分可以有不同的写法:
1、没有形参或多个形参(超过1个),需要带():
() -> {
// 方法体
}
(t1, t2[, ……]) {
// 方法体
}
2、一个形参,可以省略():
(t) -> {
// 方法体
}
t -> {
// 方法体
}
根据方法体中代码行数的不同,方法体部分也有不同的写法:
1、一行代码,可以省略{}(此时该行代码的return和;也必须省略):
() -> {
System.out.println("Hello World!");
}
() -> System.out.println("Hello World!")
() -> {
return "Hello World!"
}
() -> "Hello World!"
2、多行代码,不可以省略{}:
() -> {
System.out.println("Hello");
System.out.println("World!");
}
() -> {
System.out.println("Hello");
return "Hello World!"
}
2.2 案例
- 定义函数式接口,模拟不同类型的
lambda表达式:
public class FunctionInterface {
interface AcceptEmpty {
void accept();
}
interface AcceptOne<T> {
void accept(T t);
}
interface AcceptMore<T, E> {
void accept(T t, E e);
}
interface ReturnVoid {
void returnVoid();
}
interface ReturnR<R> {
R returnR();
}
}
- 定义调用类,接收不同的
lambda表达式:
/**
* 调用函数式接口的服务类
* @param <T> 第一个形参类型
* @param <E> 第二个形参类型
* @param <R> 返回值类型
*/
public class Service<T, E, R> {
private T t;
private E e;
public Service(T t, E e) {
this.t = t;
this.e = e;
}
void acceptEmpty(FunctionInterface.AcceptEmpty acceptEmpty) {
acceptEmpty.accept();
}
void acceptOne(FunctionInterface.AcceptOne<T> acceptOne) {
acceptOne.accept(this.t);
}
void acceptMore(FunctionInterface.AcceptMore<T, E> acceptMore) {
acceptMore.accept(this.t, this.e);
}
void returnVoid(FunctionInterface.ReturnVoid returnVoid) {
returnVoid.returnVoid();
}
R returnR(FunctionInterface.ReturnR<R> returnR) {
return returnR.returnR();
}
}
- 创建服务类实例:
Service<Integer, Integer, String> service = new Service<>(1, 2);
1、没有形参
service.acceptEmpty(new FunctionInterface.AcceptEmpty() {
@Override
public void accept() {
System.out.println("没有形参");
}
});
service.acceptEmpty(() -> {
System.out.println("没有形参");
});
service.acceptEmpty(() -> System.out.println("没有形参"));
2、一个形参
service.acceptOne(new FunctionInterface.AcceptOne<Integer>() {
@Override
public void accept(Integer t) {
System.out.println(t);
}
});
service.acceptOne((t) -> System.out.println(t));
service.acceptOne(t -> System.out.println(t));
3、多个形参
service.acceptMore(new FunctionInterface.AcceptMore<Integer, Integer>() {
@Override
public void accept(Integer t, Integer e) {
System.out.println(t);
System.out.println(e);
}
});
service.acceptMore((t, e) -> {
System.out.println(t);
System.out.println(e);
});
4、没有返回值
service.returnVoid(new FunctionInterface.ReturnVoid() {
@Override
public void returnVoid() {
System.out.println("没有返回值");
}
});
service.returnVoid(() -> System.out.println("没有返回值"));
5、有返回值
service.returnR(new FunctionInterface.ReturnR<String>() {
@Override
public String returnR() {
return "3";
}
});
service.returnR(() -> "3");
3 执行逻辑
lambda表达式本质上是传递了一个动态生成的匿名对象,是一种假的函数式编程。
lambda表达式形式上看起来很像是函数式编程:将一个函数当作形参传给方法。
实际上,lambda表达式只是Java的一个语法糖,它本质上仍然是一个普通的Java对象。
在执行的过程中,lambda表达式最终还是会被解析成匿名的接口实现类对象。
由于多态特性,在执行过程中,调用是外部传进来的实现类实例的代码。
在这个过程中,我们甚至可以将该匿名对象保存起来,便于后续多次调用。
- 定义一个函数式接口:
public interface Lambda<T, R> {
R method(T t);
}
- 定义调用类:
public class FakeFunctionalProgramming<T, R> {
private T t;
private Lambda<T, R> lambda;
public void setT(T t) {
this.t = t;
}
public void setLambda(Lambda<T, R> lambda) {
this.lambda = lambda;
}
public void doSomeThing() {
T t = before();
R r = lambda.method(t);
after(r);
}
public T before() {
return t;
}
public void after(R r) {
System.out.println(r);
}
}
- 执行以下代码:
FakeFunctionalProgramming<String, String> ffp = new FakeFunctionalProgramming<>();
ffp.setT("Xianhuii");
ffp.setLambda((t) -> "Hello " + t + "!");
ffp.doSomeThing(); // Hello Xianhuii!
从上述结果可以看出,lambda表达式的编程方式本质上是利用了多态的特性,同时又使用了模板方法模式:
- 调用处接收一个接口实例
Lambda<T, R>作为形参。 - 执行
before()方法,处理相对固定的前处理逻辑。 - 将执行过程中相关值作为形参传给
Lambda<T, R>实例,进行特定处理。 - 接收
Lambda<T, R>特定处理后的返回值。 - 执行
after()方法,处理相对固定的后处理逻辑。
此时,我们应该能够透彻理解lambda表达式中形参的来源,返回值的去向了。
借助Java多态特性,以及JVM动态生成匿名实现类实例的功能,lambda表达式才表现得那么像是函数式编程。
Java lambda表达式基本使用的更多相关文章
- Java Lambda表达式初探
Java Lambda表达式初探 前言 本文受启发于Trisha Gee在JavaOne 2016的主题演讲Refactoring to Java 8. Java 8已经发行两年多,但很多人仍然在使用 ...
- Java Lambda表达式入门
Java Lambda表达式入门 http://blog.csdn.net/renfufei/article/details/24600507 Java 8十个lambda表达式案例 http://w ...
- Java Lambda表达式入门[转]
原文链接: Start Using Java Lambda Expressions http://blog.csdn.net/renfufei/article/details/24600507 下载示 ...
- Java Lambda表达式教程与示例
Lambda表达式是Java 8中引入的一个新特性.一个lambda表达式是一个匿名函数,而且这个函数没有名称且不属于任何类.lambda表达式的概念最初是在LISP编程语言中引入的. Java La ...
- Java Lambda表达式forEach无法跳出循环的解决思路
Java Lambda表达式forEach无法跳出循环的解决思路 如果你使用过forEach方法来遍历集合,你会发现在lambda表达式中的return并不会终止循环,这是由于lambda的底层实现导 ...
- 「Flink」使用Java lambda表达式实现Flink WordCount
本篇我们将使用Java语言来实现Flink的单词统计. 代码开发 环境准备 导入Flink 1.9 pom依赖 <dependencies> <dependency> < ...
- java lambda表达式学习笔记
lambda是函数式编程(FP,functional program),在java8中引入,而C#很早之前就有了.在java中lambda表达式是'->',在C#中是‘=>’. 杜甫说:射 ...
- 《Java基础知识》Java Lambda表达式
接触Lambda表达式的时候,第一感觉就是,这个是啥?我居然看不懂,于是开始寻找资料,必须弄懂它. 先来看一个案例: @FunctionalInterface public interface MyL ...
- Java lambda 表达式常用示例
实体类 package com.lkb.java_lambda.dto; import lombok.Data; /** * @program: java_lambda * @description: ...
- Java lambda 表达式详解(JDK 8 新特性)
什么是 lambda 表达式 lambda 表达式(拉姆达表达式)是 JAVA 8 中提供的一种新的特性,它使 Java 也能进行简单的"函数式编程". lambda 表达式的本质 ...
随机推荐
- avue常用场景记录
接手的一个项目使用的是avue这个傻瓜式的专门给后端人员用的框架,文档不够友好,使用起来各种蛋疼(咱专业前端基本上不使用).为此,专门记录一下.当前avue版本2.8.12,如果要切换avue的版本, ...
- 通过 Docker 部署 Mysql 8.0 主从模式
文章转载自:http://www.mydlq.club/article/106/ 系统环境: Mysql 版本:8.0.23 Docker 版本:19.03.13 一.为什么需要 Mysql 主从复制 ...
- 不给字段创建索引,字段不存放在source中,字段无法聚合查询等
某个字段不被搜索,也就是说不想为这个字段建立inverted index(反向索引),可以这么做: PUT twitter { "mappings": { "uid&qu ...
- Elasticsearch:管理 Elasticsearch 内存并进行故障排除
文章转载自:https://elasticstack.blog.csdn.net/article/details/116974695
- MySQL 安装(二进制版)
MySQL 的安装方式一般分为三种,二进制版本.编译版本.RPM 包.比较常见的是二进制版本安装,方便简单,相对于编译安装,如果不是追求极致性能,使用起来差别不大.本次教程以二进制版本为例,系统为 c ...
- PAT (Basic Level) Practice 1002 写出这个数 分数 20
读入一个正整数 n,计算其各位数字之和,用汉语拼音写出和的每一位数字. 输入格式: 每个测试输入包含 1 个测试用例,即给出自然数 n 的值.这里保证 n 小于 10100. 输出格式: 在一行内输出 ...
- 1.2.2 musl pwn
1.2.2 musl pwn 几个结构 __malloc_context(与glibc中的main_arena类似) struct malloc_context { uint64_t secret; ...
- Vue学习之---浏览器本地存储(8/17)
博客园(纯干货):https://www.cnblogs.com/zheng-yuzhu/ 文章目录 1.基础知识 2.代码实例(localStorage.html) 3.测试效果 4.代码实例(se ...
- 齐博x1标签实例:标签如何调用论坛内容
论坛的内容不像CMS其它模块可以直接用变量 {$rs.content} 因为论坛的内容数据表是放在另一个表的,单独分开的. 当前也是为了考试效率问题而这样设计的. 所以他的调用要用下面的代码 {:fu ...
- 如何用webgl(three.js)搭建一个3D库房,3D仓库3D码头,3D集装箱,车辆定位,叉车定位可视化孪生系统——第十五课
序 又是快两个月没写随笔了,长时间不总结项目,不锻炼文笔,一开篇,多少都会有些生疏,不知道如何开篇,如何写下去.有点江郎才尽,黔驴技穷的感觉. 写随笔,通常三步走,第一步,搭建框架,先把你要写的内容框 ...