JAVA8 函数式编程(1)- Lambda表达式
1 简介
简洁的代码就能处理大型数据集合,让复杂的集合处理算法高效的运行在多核CPU上。
面向对象编程是对数据进行抽象,而函数式编程是对行为进行抽象,能编写出更易读的代码——这种代码更多地表达了业务逻辑的意图,而不是它的实现机制。
写回调函数和事件处理程序时,程序员不必再纠缠于匿名内部类的冗繁和可读性,函数式编程让事件处理系统变得更加简单。能将函数方便地传递也让编写惰性代码变得容易,惰性代码在真正需要时才初始化变量的值。
函数式编程核心思想:在思考问题时,使用不可变值和函数,函数对一个值进行处理,映射成另一个值。
2 Lambda 表达式
2.1 Lambda 表达式的基本形式
例2-1:使用匿名内部类将行为和按钮单击进行关联
button.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent event) {
System.out.println("button clicked");
}
});
这是一个代码即数据的例子,给按钮传递了代表某种行为的对象。设计匿名内部类的目的,就是为了方便 Java 程序员将代码作为数据传递。
例2-2:使用 Lambda 表达式将行为和按钮单击进行关联
button.addActionListener(event -> System.out.println("button clicked"));
Lambda 特点
- 传入了一段代码块(无名函数),event是参数名,
->将参数和主体分开。 - 声明 event 参数的方式,匿名内部类需要显示声明参数类型,而 Lambda 表达式中无需指定类型,程序依然可以编译。这是因为 javac 根据程序的上下文( addActionListener 方法的签名)在后台推断出了参数 event 的类型。这意味着如果参数类型不言而明,则无需显式指定。
Java 仍然是一种静态类型语言。声明参数时也可以包括类型信息,而且有时编译器不一定能根据上下文推断出参数的类型!
2.2 Lambda 表达式的变种
例2-3:Lambda 表达式的不同形式
// 1 不含参数,且返回类型为void
Runnable noArguments = () -> System.out.println("Hello World");
// 2 一个参数,可省略括号
ActionListener oneArgument = event -> System.out.println("button clicked");
// 3 主体不仅可以是表达式,也可以是代码块{},块中可以返回或抛异常
Runnable multiStatement = () -> {
System.out.print("Hello");
System.out.println(" World");
};
// 4 创建了一个函数,用来计算两个数字相加的结果。变量 add 的类型是 BinaryOperator<Long> ,它不是两个数字的和,而是将两个数字相加的那行代码。
BinaryOperator<Long> add = (x, y) -> x + y;
// 5 需要显示声明参数类型时,用()
BinaryOperator<Long> addExplicit = (Long x, Long y) -> x + y;
目标类型是指 Lambda 表达式的类型,依赖于上下文环境,由编译器推断而来。比如,将 Lambda 表达式赋值给一个局部变量,或传递给一个方法作为参数,局部变量或方法参数的类型就是 Lambda 表达式的目标类型。Java 中初始化数组时,数组的类型就是根据上下文推断出来的。另一个常见的例子是 null ,只有将 null 赋值给一个变量,才能知道它的类型。
2.3 Lambda 的引用类型
Lambda 表达式引用的是值,而不是变量。
Lambda 和匿名内部类一样,在引用外部变量时,该变量必须是终态变量。如下是正确和错误示例:
例2-4:Lambda 表达式中引用既成事实上的 final 变量
// 编译通过
String name = getUserName(); // final变量
button.addActionListener(event -> System.out.println("hi " + name));
// 编译错误
String name = getUserName();
name = formatUserName(name); // 非终态变量
button.addActionListener(event -> System.out.println("hi " + name));
Java 8 无需显示声明final,但是该变量必须是既成事实上的终态变量(final),否则编译器会报错。是否显示的使用final,取决于个人喜好。
2.4 Lambda 表达式的类型
Lambda 表达式的类型是函数接口。
函数接口是只有一个抽象方法的接口.
例2-5: ActionListener 接口
public interface ActionListener extends EventListener {
public void actionPerformed(ActionEvent event);
}
ActionListener 就是一个函数接口,actionPerformed 定义在一个接口里,因此 abstract 关键字不是必需
的。该接口也继承自一个不具有任何方法的父接口: EventListener 。
接口中单一方法的命名并不重要,只要方法签名和 Lambda 表达式的类型匹配即可。
表2-1:JDK8 中重要的函数接口
| 接口 | 参数 | 返回类型 |
|---|---|---|
Predicate<T> |
T | boolean |
Consumer<T> |
T | void |
Function<T,R> |
T | R |
Supplier<T> |
None | T |
UnaryOperator<T> |
T | T |
BinaryOperator<T> |
(T, T) | T |
2.5 Lambda 表达式的类型推断
Lambda 表达式中的类型推断,实际上是 Java 7 就引入的目标类型推断的扩展。
例2-6:使用菱形操作符,根据变量类型做推断
Map<String, Integer> diamondWordCounts = new HashMap<>();
如果将构造函数直接传递给一个方法,也可根据方法签名来推断类型。
例2-7:使用菱形操作符,根据方法签名做推断
useHashmap(new HashMap<>());
...
private void useHashmap(Map<String, String> values);
Java 8 中对类型推断系统做了提升。上面的例子将 new HashMap<>()传给 useHashmap 方法,即使编译器拥有足够的信息,也无法在 Java 7 中通过编译。
类型推断:JAVA8中,程序员可省略 Lambda 表达式中的所有参数类型。javac 根据 Lambda 表达式上下文信息就能推断出参数的正确类型。程序依然要经过类型检查来保证运行的安全性,但不用再显式声明类型。
例2-8:Predicate类型推断
// Predicate 接口的源码,接受一个对象,返回一个布尔值
public interface Predicate<T> {
boolean test(T t);
}
// x被推断为Integer, javac 还检查Lambda 表达式的返回值是不是 boolean
Predicate<Integer> atLeast5 = x -> x > 5;
例2-9:BinaryOperator类型推断
// BinaryOperator 接口源码,接受两个对象,返回一个对象,泛型参数既是入参类型,也是返回类型
public interface BinaryOperator<T> extends BiFunction<T,T,T> {}
public interface BiFunction<T, U, R> {
R apply(T t, U u);
}
// x,y推断为Long,并返回Long
BinaryOperator<Long> addLongs = (x, y) -> x + y;
// 以下代码编译报错:Operator '& #x002B;' cannot be applied to java.lang.Object, java.lang.Object. 没有给出add的泛型信息,编译器认为都是Object。
BinaryOperator add = (x, y) -> x + y;
2.6 总结
- Lambda 表达式是一个匿名方法,将行为像数据一样进行传递。
- Lambda 表达式的常见结构:
BinaryOperator<Integer> add = (x, y) → x + y。 - Lambda 表达式的类型是函数接口,指仅具有单个抽象方法的接口。
练习:使用 ThreadLocal 创建一个线程安全的 DateFormatter 对象
ThreadLocal 作为容器保存当前线程的局部变量,JAVA8 中新增了工厂方法,可以不用再使用继承;
DateFormatter 非线程安全
public class JavaMainTest {
private static ThreadLocal<SimpleDateFormat> threadLocal;
private void init() {
// jdk7
threadLocal = new DataFormatThreadLocal() ;
// jdk8
threadLocal = ThreadLocal.withInitial(new Supplier<SimpleDateFormat>() {
@Override
public SimpleDateFormat get() {
return new SimpleDateFormat("yyyy-MM-dd");
}
});
// jdk8 lambda
threadLocal = ThreadLocal.withInitial(() -> new SimpleDateFormat("yyyy-MM-dd"));
}
class DataFormatThreadLocal extends ThreadLocal<SimpleDateFormat> {
@Override
protected SimpleDateFormat initialValue() {
// TODO Auto-generated method stub
return new SimpleDateFormat("yyyy-MM-dd");
}
}
}
3 参考资料
《Java 8函数式编程》- 作者:[英]沃伯顿;译者:王群锋
JAVA8 函数式编程(1)- Lambda表达式的更多相关文章
- Java8函数式编程以及Lambda表达式
第一章 认识Java8以及函数式编程 尽管距离Java8发布已经过去7.8年的时间,但时至今日仍然有许多公司.项目停留在Java7甚至更早的版本.即使已经开始使用Java8的项目,大多数程序员也仍然采 ...
- Java8函数式编程和lambda表达式
文章目录函数式编程JDK8接口新特性函数接口方法引用函数式编程函数式编程更多时候是一种编程的思维方式,是一种方法论.函数式与命令式编程区别主要在于:函数式编程是告诉代码你要做什么,而命令式编程则是告诉 ...
- Java 函数式编程(Lambda表达式)与Stream API
1 函数式编程 函数式编程(Functional Programming)是编程范式的一种.最常见的编程范式是命令式编程(Impera Programming),比如面向过程.面向对象编程都属于命令式 ...
- Java 函数式编程和Lambda表达式
1.Java 8最重要的新特性 Lambda表达式.接口改进(默认方法)和批数据处理. 2.函数式编程 本质上来说,编程关注两个维度:数据和数据上的操作. 面向对象的编程泛型强调让操作围绕数据,这样可 ...
- 【Java SE进阶】Day12 函数式接口、函数式编程(Lambda表达式)
一.函数式接口介绍 1.概念 仅有一个抽象方法的接口 适用于函数式编程(Lambda使用的接口) 语法糖:方便但原理不变,如for-each是Iterator的语法糖 Lambda≈匿名内部类的语法糖 ...
- Java8函数式接口以及lambda表达式实践
罗列一下遇到可以转换成lamada表达式的场景,仅供参考,如有更好的方式,欢迎在评论区留言. 1.计算订单总金额 订单总金额一般是在后台循环叠加每个购买商品的金额已获取到,通常的方式如下 BigDec ...
- 读Java8函数式编程笔记01_Lambda表达式
1. Java 8函数式编程 1.1. 没有单子 1.2. 没有语言层面的惰性求值 1.3. 没有为不可变性提供额外支持 1.4. 集合类可以拥有一些额外的方法:default方法 2. 现实世界中, ...
- Python函数式编程:Lambda表达式
首先我们要明白在编程语言中,表达式和语句的区别. 表达式是一个由变量.常量.有返回值的函数加运算符组成的一个式子,该式子是有返回值的 ,如 a + 1 就是个表达式, 单独的一个常量.变量 或函数调 ...
- C# 函数式编程 —— 使用 Lambda 表达式编写递归函数
最近看了赵姐夫的这篇博客http://blog.zhaojie.me/2009/08/recursive-lambda-expressions.html,主要讲的是如何使用 Lambda 编写递归函数 ...
- 函数式编程--使用lambda表达式
前面一篇博客我们已经说到了,lambda表达式允许使用更简洁的代码来创建只有一个抽象方法的接口的实例.现在我们来写一段java的命令者模式来自己研究下lambda表达式的语法. 这里重复下命令者模式: ...
随机推荐
- 关于在有动态的Scroll Bar情况下页面内容的对齐问题
关于在有动态的Scroll Bar情况下页面内容的对齐问题 问题场景 一个标题行 + 一些内容行 要求在内容行超过指定行数时 将多出的行隐藏,并展示Scroll Bar的来提示用户可以下划查看更多内容 ...
- 一文彻底熟练掌握并使用Java的NIO操作
一.基本概念 Java NIO 是 Java 1.4 引入的,用于处理高速.高并发的 I/O 操作.与传统的阻塞 I/O 不同,NIO 支持非阻塞 I/O 和选择器,可以更高效地管理多个通道. 二.核 ...
- nginx记录日志时记录服务器响应的内容
目前的 nginx 是不支持输出 response 报文体的 使用body_filter_by_lua来分配请求报文体给一个nginx变量.下面是一个示例 worker_processes 1; er ...
- 文件上传日志包含详解与CTF实战
1. 日志简介 1.1 日志介绍 日志是记录系统或应用程序运行时事件的文件.这些记录可以包括错误信息.用户活动.系统性能指标等,帮助开发者和管理员监控和排查问题. 日志通常会记录多种内容,包括: 时间 ...
- 关于总线协议的记忆技巧—i2c、spi
口诀: 钟高 数下 是开始,(解释,时钟线保持高时,数据线由高拉到低是向下趋势,说明是"开始信号") 钟高 数上 是停止.(解释,时钟线保持高时,数据线由低拉到高是向上趋势,说明是 ...
- Python预安装包制作
预编译安装包 在Linux服务器上,经常会安装Python.Redis.Nginx等服务,不管离线.在线都需要编译.编译之前还需要安装一些依赖的环境,比如,openssl.gcc.g++等,但是mak ...
- 2023NOIP A层联测32 T4 红楼 ~ Eastern Dream
2023NOIP A层联测32 T4 红楼 ~ Eastern Dream 根号分治加分块. Ps:分块后面真的用的多. 思路 考虑根号分治,将 \(x\) 分为 \(x \leq \sqrt n\) ...
- MMORPG技能管线设计经验总结
导语: 表现丰富.机制多变的技能作为MMORPG游戏战斗体验的核心组成部分,是吸引玩家的一大亮点,本文总结了在MMORPG技能系统设计上的一些经验,供大家参考. 1.设计思路 早期的MMORPG手游中 ...
- 『玩转Streamlit』--布局与容器组件
在Streamlit中,布局类组件扮演着至关重要的角色. 它们不仅决定了应用程序的视觉呈现和用户体验,也极大地增强了页面内容的组织性和可读性. 通过这些组件,开发者可以灵活地划分页面空间,创建出清晰. ...
- Java通用分页
一. 要分页我们必须要有数据库,所以我们先准备下数据库,其数据库脚步如下: --以下是创建数据库和数据库表以及向数据库插入数据 use master Go if exists(select ...