Java 基础 —— Lambda 表达式
概述
阅读项目代码时,尤其是阅读一些源码时,经常会遇到 Lambda 表达式。对此之前看过相关文章,但是停留在模模糊糊的印象上。今天趁着有时间,通过一些 demo 示例,梳理一下它的用法,以备后期遗忘的时候快速查询它的用法!
Lambda 表达式是 Java 8 的重要更新,它支持将代码块作为方法参数、允许使用更简洁的代码来创建只有一个抽象方法的接口的实例。
描述中提到的接口称为函数式接口
语法
Lambda 表达式的主要作用就是可以用于简化创建匿名内部类对象,Lambda 表达式的代码块将会用于实现抽象方法的方法体,Lambda 表达式就相当于一个匿名方法。
Lambda 表达式由三部分组成:
- 形参列表:形参列表允许省略类型,如果形参列表中只有一个参数,形参列表的圆括号也可以省略;
- 箭头(
->
):通过英文画线和大于符号组成; - 代码块:如果代码块只有一条语句,花括号可以省略。Lambda 代码块只有一条 return 语句,可以省略 return 关键字,Lambda 表达式会自动返回这条语句的值作为返回值。
示例
interface Eatable {
void taste();
}
interface Flyable {
void fly(String weather);
}
interface Addable {
int add(int a, int b);
}
public class LambdaQs {
// 调用该方法需要传入一个 Eatable 类型的对象
public void eat(Eatable e) {
System.out.println(e);
e.taste();
}
// 调用该方法需要传入 Flyable 类型的对象
public void drive(Flyable f) {
System.out.println("我正在驾驶:" + f);
f.fly("「夏日晴天」");
}
// 调用该方法需要 Addable 类型的对象
public void calc(Addable add) {
System.out.println("5 + 3 = " + add.add(5, 3));
}
public static void main(String[] args) {
LambdaQs lq = new LambdaQs();
// Lambda 表达式的代码块只有一句,因此省略了花括号
lq.eat(() -> System.out.println("雪糕的味道不错!"));
// Lambda 表达式的形参只有一个参数,因此省略了圆括号
lq.drive(weather -> {
// 对接口中抽象方法 fly 的重写
System.out.println("今天天气是:" + weather);
System.out.println("飞机平稳飞行!");
});
// Lambda 表达式只有一条语句,即使该表达式需要返回值,也可以省略 return
lq.calc((a, b) -> a + b);
// 如果不用 Lambda 表达式,就需要如下匿名类的方式去重写抽象方法
lq.calc(new Addable() {
@Override
public int add(int a, int b) {
return a + b;
}
});
}
}
输出结果:
oop.lambda.LambdaQs$$Lambda$1/1607521710@7ef20235
雪糕的味道不错!
我正在驾驶:oop.lambda.LambdaQs$$Lambda$2/1329552164@15aeb7ab
今天天气是:「夏日晴天」
飞机平稳飞行!
5 + 3 = 8
5 + 3 = 8
以上示例可以说明,Lambda 表达式实际上可以被当做一个具体的对象。
Lambda 表达式与函数式接口
Lambda 表达式的类型,也被称为「目标类型(target type
)」。Lambda 表达式的目标类型必须是「函数式接口(functional interface
)」。函数式接口代表只包含一个抽象方法的接口。函数式接口可以包含多个默认方法、类方法,但仅能声明一个抽象方法。
查询 Java 8 的 API 文档,可以发现大量的函数式接口,例如:Runnable、ActionListener 等接口都是函数式接口。
Java 8 专门为函数式接口提供了
@FunctionalInterface
注解。该注解就是用于告诉编译器校验接口必须是函数式接口,否则就报错。
由于 Lambda 表达式的结果就是被当做对象/实例,因此,可以使用 Lambda 表达式进行赋值,示例:
Runnable r = () -> {
for (int i = 0; i < 100; i++) {
System.out.println(i);
}
};
我们看一下 Runnable 接口的定义:
@FunctionalInterface
public interface Runnable {
public abstract void run();
}
看一个错误示例:
Object obj = () -> {
for (int i = 0; i < 100; i++) {
System.out.println(i);
}
};
上面这段代码会报错:Target type of a lambda conversion must be an interface
。Lambda 表达式的目标类型必须是明确的函数式接口!将 Lambda 表达式赋值给 Object 类型的变量,编译器只能推断出它的表达类型为 Object,而 Object 并不是函数式接口,因此就报错了!
为了保证 Lambda 表达式的目标类型是明确的函数式接口,有如下三种常见方式:
- 将 Lambda 表达式赋值给函数式接口类型的变量;
- 将 Lambda 表达式作为函数式接口类型的参数传给某个方法;
- 使用函数式接口对 Lambda 表达式进行强制类型转换;
将上面出错的代码可以进行如下的改写:
Object obj1 = (Runnable)() -> {
for (int i = 0; i < 100; i++) {
System.out.println(i);
}
};
综上,Lambda 表达式的本质很简单,就是使用简单的语法来创建函数式接口的实例,避免匿名内部类的繁琐。
方法引用于构造器引用
如果 Lambda 表达式的代码块只有一条代码,还可以在代码中使用方法引用和构造器引用。
方法引用和构造器引用的好处是使 Lambda 表达式的代码块更加简洁。方法引用和构造器引用都需要使用两个英文冒号 ::
。
种类 | 示例 | 说明 | 对应的 Lambda 表达式 |
---|---|---|---|
引用类方法 | 类名::类方法 | 函数式接口中被实现的方法的全部参数传给该类方法作为参数 | (a,b,...) -> 类名.类方法(a,b,...) |
引用特定对象的实例方法 | 特定对象::实例方法 | 函数式接口中被实现的方法的全部参数传给该方法作为参数 | (a,b,...) -> 特定对象.实例方法(a,b,...) |
引用某类对象的实例方法 | 类名::实例方法 | 函数式接口中被实现的方法的第一个参数作为调用者,后面的参数全部传给该方法作为参数 | (a,b,...)->a.实例方法(b,...) |
引用构造器 | 类名::new | 函数式接口中被实现方法的全部参数传给该构造器作为参数 | (a,b,...)->new 类名(a,b,...) |
@FunctionalInterface
interface Converter {
Integer convert(String from);
}
@FunctionalInterface
interface MyTest {
String test(String a, int b, int c);
}
@FunctionalInterface
interface YourTest {
// 抽象方法负责根据 String 参数生成一个 JFrame 返回值
JFrame win(String title);
}
public class LambdaRef {
public static void main(String[] args) {
// 1 引用类方法
// 下面使用 Lambda 表达式创建 Converter 对象
Converter converter1 = from -> Integer.valueOf(from);
Integer val = converter1.convert("99");
// 函数式接口中被实现方法的全部参数传给该类方法作为参数
Converter converter2 = Integer::valueOf;
Integer val2 = converter2.convert("100");
// 2 引用特定对象的实例方法
// 使用 Lmabda 表达式创建 Converter 对象
Converter converter3 = from -> "hello michael翔".indexOf(from);
// 调用 "hello michael翔"的indexOf()实例方法
// 函数式接口中被实现的全部参数传给该方法作为参数
Converter converter4 = "hello michael翔"::indexOf;
// 3 引用某类对象的实例方法
// 使用 Lambda 表达式创建 MyTest 对象
MyTest mt = (a, b, c) -> a.substring(b, c);
String str = mt.test("Hello World, Hello Michael翔", 2,9);
// 上面 Lambda 表达式只有一行,因此可以使用如下引用进行替换
// 函数式接口中被实现方法的第一个参数作为调用者
// 后面的参数全部传给该方法作为参数
MyTest str2 = String::substring;
// 4 引用构造器
// 使用 Lambda 表达式创建 YourTest 对象
YourTest yt = a -> new JFrame(a);
JFrame jf = yt.win("窗口");
// 使用构造器引用进行替换
// 函数式接口中被实现方法的全部参数传给该构造器作为参数
YourTest yt2 = JFrame::new;
JFrame jf2 = yt.win("窗口2");
}
}
Lambda 表达式与匿名内部类的联系与区别
Lambda 表达式与匿名内部类存在如下相同点:
- Lambda 表达式与匿名内部类一样,都可以直接访问
effectively final
的局部变量,以及外部类的成员变量(包括示例变量和类变量); - Lambda 表达式创建的对象与匿名内部类生成的对象一样,都可以直接调用从接口中继承的默认方法;
Lambda 表达式与匿名内部类的区别:
- 匿名内部类可以为任意接口创建实例,不管接口包含多少个抽象方法,只要匿名内部类实现所有抽象方法即可;但是 Lambda 表达式只能为函数式接口创建实例;
- 匿名内部类可以为抽象类甚至普通类创建实例,但是 Lambda 表达式只能为函数式接口创建实例;
- 匿名内部类实现的抽象方法体允许调用接口中定义的默认方法,但是 Lambda 表达式的代码块不允许调用接口中定义的默认方法;
@FunctionalInterface
interface Converter {
Integer convert(String from);
}
@FunctionalInterface
interface MyTest {
String test(String a, int b, int c);
}
@FunctionalInterface
interface YourTest {
// 抽象方法负责根据 String 参数生成一个 JFrame 返回值
JFrame win(String title);
}
public class LambdaRef {
public static void main(String[] args) {
// 1 引用类方法
// 下面使用 Lambda 表达式创建 Converter 对象
Converter converter1 = from -> Integer.valueOf(from);
Integer val = converter1.convert("99");
// 函数式接口中被实现方法的全部参数传给该类方法作为参数
Converter converter2 = Integer::valueOf;
Integer val2 = converter2.convert("100");
// 2 引用特定对象的实例方法
// 使用 Lmabda 表达式创建 Converter 对象
Converter converter3 = from -> "hello michael翔".indexOf(from);
// 调用 "hello michael翔"的indexOf()实例方法
// 函数式接口中被实现的全部参数传给该方法作为参数
Converter converter4 = "hello michael翔"::indexOf;
// 3 引用某类对象的实例方法
// 使用 Lambda 表达式创建 MyTest 对象
MyTest mt = (a, b, c) -> a.substring(b, c);
String str = mt.test("Hello World, Hello Michael翔", 2,9);
// 上面 Lambda 表达式只有一行,因此可以使用如下引用进行替换
// 函数式接口中被实现方法的第一个参数作为调用者
// 后面的参数全部传给该方法作为参数
MyTest str2 = String::substring;
// 4 引用构造器
// 使用 Lambda 表达式创建 YourTest 对象
YourTest yt = a -> new JFrame(a);
JFrame jf = yt.win("窗口");
// 使用构造器引用进行替换
// 函数式接口中被实现方法的全部参数传给该构造器作为参数
YourTest yt2 = JFrame::new;
JFrame jf2 = yt.win("窗口2");
}
}
Lambda 表达式调用 Arrays 的类方法
Arrays 类的有些方法需要 Comparator、XxxOperator、XxxFunction 等接口的实例,这些接口都是函数式接口。因此,可以使用 Lambda 表达式来调用 Arrays 的方法。
public class LambdaArrays {
public static void main(String[] args) {
String[] arr1 = new String[]{"java", "python", "rust", "go"};
Arrays.parallelSort(arr1, (o1, o2) -> o1.length() - o2.length());
System.out.println(Arrays.toString(arr1));
int[] arr2 = {3, -4, 25, 16, 30, 18};
// left 代表数组中前一个索引处的元素,计算第一个元素时,left 为 1;
// right 代表数组中的当前索引处的元素
Arrays.parallelPrefix(arr2, (left, right) -> left * right);
System.out.println(Arrays.toString(arr2));
long[] arr3 = new long[5];
// a 代表正在计算的元素索引
Arrays.parallelSetAll(arr3, a -> a * 5);
System.out.println(Arrays.toString(arr3));
// 等价于用匿名内部类重写 applyAsLong 抽象方法
Arrays.parallelSetAll(arr3, new IntToLongFunction() {
@Override
public long applyAsLong(int value) {
return value * 5;
}
});
System.out.println(Arrays.toString(arr3));
}
}
输出:
[go, java, rust, python]
[3, -12, -300, -4800, -144000, -2592000]
[0, 5, 10, 15, 20]
[0, 5, 10, 15, 20]
因为这些要出入 Comparator、XxxOperator、XxxFunction 等接口的实例往往都是一次性的,使用 Lambda 表达式也不用考虑重用等,反而让程序更加简洁了。
总结
本文主要参考的是 《疯狂 Java 讲义第 5 版》的第 6 章的面向对象下,通过实际的示例 demo 应该可以将 Lambda 的常用场景和用法掌握了。这样,看项目代码或者源码的话,会更加易于理解!基本功扎实,才能走得更快!
参考
Java 基础 —— Lambda 表达式的更多相关文章
- Java基础学习总结(44)——10个Java 8 Lambda表达式经典示例
Java 8 刚于几周前发布,日期是2014年3月18日,这次开创性的发布在Java社区引发了不少讨论,并让大家感到激动.特性之一便是随同发布的lambda表达式,它将允许我们将行为传到函数里.在Ja ...
- Java 8 Lambda 表达式详解
一.Java 8 Lambda 表达式了解 参考:Java 8 Lambda 表达式 | 菜鸟教程 1.1 介绍: Lambda 表达式,也可称为闭包,是推动 Java 8 发布的最重要新特性. La ...
- Java 8 Lambda表达式
Java 8 Lambda表达式探险 http://www.cnblogs.com/feichexia/archive/2012/11/15/Java8_LambdaExpression.html 为 ...
- 深入浅出 Java 8 Lambda 表达式
摘要:此篇文章主要介绍 Java8 Lambda 表达式产生的背景和用法,以及 Lambda 表达式与匿名类的不同等.本文系 OneAPM 工程师编译整理. Java 是一流的面向对象语言,除了部分简 ...
- Java 8 Lambda表达式10个示例【存】
PS:不能完全参考文章的代码,请参考这个文件http://files.cnblogs.com/files/AIThink/Test01.zip 在Java 8之前,如果想将行为传入函数,仅有的选择就是 ...
- Java 8 Lambda 表达式
Lambda 是啥玩意 简单来说,Lambda 就是一个匿名的方法,就这样,没啥特别的.它采用一种非常简洁的方式来定义方法.当你想传递可复用的方法片段时,匿名方法非常有用.例如,将一个方法传递给另外一 ...
- Java 8 lambda表达式示例
例1.用lambda表达式实现Runnable 我开始使用Java 8时,首先做的就是使用lambda表达式替换匿名类,而实现Runnable接口是匿名类的最好示例.看一下Java 8之前的runna ...
- 用Java 8 Lambda表达式实现设计模式:命令模式
在这篇博客里,我将说明如何在使用 Java 8 Lambda表达式 的函数式编程方式 时实现 命令 设计模式 .命令模式的目标是将请求封装成一个对象,从对客户端的不同类型请求,例如队列或日志请求参数化 ...
- 02、Java的lambda表达式和JavaScript的箭头函数
前言 在JDK8和ES6的语言发展中,在Java的lambda表达式和JavaScript的箭头函数这两者有着千丝万缕的联系:本次试图通过这篇文章弄懂上面的两个"语法糖". 简介 ...
随机推荐
- linux下解决vim打开文件乱码现象
用VIM打开一个文件进行编辑时最下面的任务栏出现中文乱码,严重影响编写代码. 因为VIM默认的语言支持不行, 修改~/.vimrc 文件或/etc/vimrc 文件,添加一下代码: set encod ...
- 怒肝俩月,新鲜出炉史上最有趣的Java小白手册,第一版,每个 Java 初学者都应该收藏
这么说吧,在我眼里,Java 就是最流行的编程语言,没有之一(PHP 往一边站).不仅岗位多,容易找到工作,关键是薪资水平也到位,不学 Java 亏得慌,对吧? 那可能零基础学编程的小伙伴就会头疼了, ...
- EIGRP-15-其他和高级的EIGRP特性-1-路由器ID
与很多协议一样, EIGRP也使用了路由器ID (RTD)的概念,用一个4字节的编号来标识某个路由器实例.每个地址家族实例拥有自已独立的RID.工程师可以在一台路由器上,为多个EIGRP进程和地址家族 ...
- Edge浏览器现已支持Tampermonkey(油猴)
Tampermonkey,Greasemonkey,这种扩展可以让我们的浏览器自动运行我们自己定义的脚本,然后就出现了相关网站(比如https://greasyfork.org/zh-CN)让大家在上 ...
- [计网笔记] 传输层---UDP
- 【Spring注解驱动开发】面试官:如何将Service注入到Servlet中?朋友又栽了!!
写在前面 最近,一位读者出去面试前准备了很久,信心满满的去面试.没想到面试官的一个问题把他难住了.面试官的问题是这样的:如何使用Spring将Service注入到Servlet中呢?这位读者平时也是很 ...
- 消息队列——RabbitMQ的基本使用及高级特性
文章目录 一.引言 二.基本使用 1. 简单示例 2. work queue和公平消费消息 3. 交换机 三.高级特性 1. 消息过期 2. 死信队列 3. 延迟队列 4. 优先级队列 5. 流量控制 ...
- 使用Kubeflow构建机器学习流水线
在此前的文章中,我已经向你介绍了Kubeflow,这是一个为团队设置的机器学习平台,需要构建机器学习流水线. 在本文中,我们将了解如何采用现有的机器学习详细并将其变成Kubeflow的机器学习流水线, ...
- Linux Pam后门总结拓展
首发先知社区: https://xz.aliyun.com/t/7902 前言 渐渐发现pam后门在实战中存在种植繁琐.隐蔽性不强等缺点,这里记录下学习pam后门相关知识和pam后门的拓展改进. 0x ...
- skywalking与pinpoint全链路追踪方案对比
由于公司目前有200多微服务,微服务之间的调用关系错综复杂,调用关系人工维护基本不可能实现,需要调研一套全链路追踪方案,初步调研之后选取了skywalking和pinpoint进行对比; 选取skyw ...