Java8(又称为 JDK8)是 Java 语言开发的一个重要版本,于2014年发布。 JDK8 可以说是里程碑式的版本,提供了很多的新功能,支持函数式编程,新的 JavaScript 引擎,新的日期 API,新的Stream API 等。 本文仅涉及编程开发中常用的特性 。
涉及 | 说明 |
---|---|
Lambda表达式 |
允许把函数作为参数传递进方法中。 |
方法引用 |
函数引用,替代lambda。 |
函数式接口 |
只有一个抽象方法的接口,可以接收函数。 |
Stream API |
流式处理,函数式编程示例。 |
默认方法 |
接口提供默认实现方法。 |
Optional API |
值容器,简化空指针处理。 |
Date-Time API |
新的日期接口。 |
函数式编程
JDK8 提供了函数式编程支持,不再是单纯的面对对象。
函数编程语言
最重要的基础是λ演算(lambda calculus)。而λ演算的函数可以接受函数当作输入和输出。
JDK8 提供了一些特性来支持函数式编程:Lambda表达式
, 函数式接口
, 方法引用
等。
这三者具有紧密联系,需要结合理解。
默认方法
和stream API
及增强API
等则另行说明。
函数式接口
只有一个抽象方法的接口。Lambda表达式
只能作用于函数式接口
。- 若
Lambda表达式
表示的逻辑已经被抽取为方法,方法引用
可以替换Lambda表达式
,简化代码。
Lambda 表达式
Lambda Expressions : 允许把函数作为参数传递进方法中。主要用来定义行内执行的 函数式接口
。
lambda表达式
:又名闭包
、匿名方法
。
函数式接口
:只有一个抽象方法的接口。
语法格式
1 | // 格式一 |
语法特征:
- 可选的参数类型声明:不需要声明参数类型,编译器可以统一识别参数值。
- 可选的参数圆括号:一个参数无需定义圆括号,但没有参数或多个参数需要定义圆括号。
- 可选的参数大括号:如果主体包含了一个语句,就不需要使用大括号。
- 可选的返回关键字:如果主体只有一个表达式返回值则编译器会自动返回值,大括号需要指定明表达式返回了一个数值。
基本语法特征示例:
1 | package tk.gushizone.jdk8.lambda; |
变量作用域
1 | public interface Converter<T1, T2> { |
Lambda表达式 的局部变量具有隐性的 final 的语义。
可以不用声明为 final,但是必须不可被后面的代码修改。
1 | int num = 1; |
在 Lambda表达式 当中不允许声明一个与局部变量同名的参数或者局部变量。
1 | String first = ""; |
方法引用
Method references : 通过方法的名字来指向一个方法。可以理解为Lambda表达式的缩写,可以使代码更加简洁。
引用的方法需要与函数式接口具有一样的参数和返回值。
语法格式
引用类型 | 语法格式 |
---|---|
构造器方法引用 | Class::new |
类静态方法引用 | Class::method |
实例特殊方法引用(第一个参数,是调用者,第二个参数(或无参)是参数) | Class::method |
实例方法引用 | instance::method |
方法引用的语法格式示例:
1 | class Car { |
1 |
|
1 | /** |
函数式接口
functional interfaces : 仅有一个抽象方法的接口,可以接收函数。
可以有多个非抽象方法,函数式接口可以包含Object的public方法(即使它们是抽象方法),因为任何一个函数式接口的实现,默认都继承了 Object 类,包含了来自 java.lang.Object 里对这些抽象方法的实现。
JDK8 引入了新注解 @FunctionalInterface
,用于函数式接口的编译级错误检查(非必须)。
常用函数式接口
使用 lambda表达式
就需要函数式接口,除了现有的函数式接口,JDK8还提供了 java.util.function包
,其包含一些常用的函数式接口。
常用接口 | 说明 |
---|---|
Function<T,R> |
函数,一个参数 T ,一个返回值 R 。 |
Supplier<T> |
供应者,无参数,返回一个结果 T 。 |
Consumer<T> |
消费者,一个参数 T ,无返回结果。 |
Predicate<T> |
断言,一个参数 T ,返回 boolean 。 |
1 | package tk.gushizone.java.jdk8.functioninterface; |
1 | package tk.gushizone.java.jdk8.functioninterface; |
Stream API
Stream(流) : 一个来自数据源的元素队列并支持聚合操作。
元素
: 特定类型的对象,形成一个队列。 Java中的Stream并不会存储元素,而是按需计算。数据源
: 流的来源。 可以是集合,数组,I/O channel, 产生器generator 等。聚合操作
: 类似SQL语句一样的操作, 比如filter, map, reduce, find, match, sorted等。和以前的Collection操作不同, Stream操作还有两个基础的特征:
Pipelining
: 中间操作都会返回流对象本身。 这样多个操作可以串联成一个管道, 如同流式风格(fluent style)。这样做可以对操作进行优化, 比如延迟执行(laziness)和短路( short-circuiting)。内部迭代
: 以前对集合遍历都是通过 Iterator 或者 For-Each 的方式, 显式的在集合外部进行迭代, 这叫做外部迭代。Stream提供了内部迭代的方式, 通过访问者模式(Visitor)实现。
得益于 默认方法
, Stream方法
被整合到了 Collection接口
中。现在可以使用类似 SQL 方式操作元素集合,十分高效简洁。
Stream操作 的一般流程 : 流化
-> 中间操作
-> 完成操作
。
1 |
|
流化
JDK8 中,集合接口提供了两种方法生成流。
涉及方法 | 说明 |
---|---|
stream() |
为集合创建串行流。 |
parallelStream() |
为集合创建并行流,相当于多线程操作。 |
1 |
|
中间操作
涉及 | 说明 | 示例 |
---|---|---|
filter(..) |
筛选出元素。 | .filter(String::isEmpty) 为筛选出所有空字符串。 |
map(..) |
映射每个元素到对应的结果。 | .map(i -> i * i) 为获取获取对应的平方数。 |
sorted() / sorted(..) |
排序(升序),通过 Comparable接口 判断。 |
.sorted(Comparator.reverseOrder()) 为降序,可传入 lambda表达式。 |
distinct() |
通过 equals方法 判断。 |
- |
count() |
统计个数。 | - |
1 |
|
完成操作
常见的 完成操作
是 归约操作
,即转换类型。
涉及 | 说明 | 示例 |
---|---|---|
collect(..) |
收集归约。一般为最终操作,配合 Collectors 使用。 |
.collect(Collectors.toList()) 为收集列表 |
reduce(..) |
聚合归约。一般为最终操作,可以实现常见的计算。 | .reduce(BigDecimal.ZERO, BigDecimal::add) 为累加求和。 |
collect
collect 接收一个 Collector
参数。Collectors 类实现了很多收集归约操作,例如将流转换成集合和聚合元素。
涉及 | 说明 | 示例 |
---|---|---|
toList() / toSet() / toMap() / toCollection(..) |
转换成集合。 | Collectors.toCollection(HashSet::new) |
joining(..) |
转换成字符串。 | Collectors.joining(", ") / Collectors.joining(", ", "{", "}") |
groupingBy(..) |
分组,根据函数式接口的返回值分组。 | Collectors.groupingBy(item -> item.getAge() >= 18) |
1 |
|
1 |
|
默认方法
Default methods : 允许将新功能添加到库的接口,并确保与为这些接口的旧版本编写的代码的二进制兼容性。
简单的说,默认方法就是接口可以有实现方法,而且不需要实现类去实现其方法。(🤣接口和抽象类都可以有实现方法了)
JDK8 以前,接口是个双刃剑,
好处是面向抽象而不是面向具体编程,
缺陷是当需要修改接口时候,需要修改全部实现该接口的类。
1 | public interface Vehicle { |
1 | public interface FourWheeler { |
1 | public class Car implements Vehicle, FourWheeler { |
1 | Vehicle:默认方法调用! |
forEach 解析
forEach
是经典的默认方法,是 jdk8 集合框架可使用的遍历方法(实现于Iterable接口)。
1 |
|
JDK8 在 Iterable接口
中新增了默认方法 forEach
。
其会遍历集合,并调用
Consumer接口
的accept方法
。
1 | default void forEach(Consumer<? super T> action) { |
Consumer接口
是 jdk8新增的 函数式接口
。
其提供的接口方法是:一个参数,没有返回值。
1 | package java.util.function; |
这里 System.out::println
和 Consumer接口
有相同的参数和返回值。
故,这里集合会遍历执行 相应逻辑,即遍历打印。
1 | names.forEach(System.out::println); |
Optional API
Optional
: 值容器, 可以简化空指针异常处理。
构造
1 |
|
ifPresent
1 | /** |
orElse
1 | /** |
filter
1 | /** |
map
1 | /** |
Date-Time API
JDK8 提供了 java.time包
来完善对日期和时间的处理,遵循 JSR310
规范, 其是建立在 Joda-Time
基础上的。
JDK8 之前,对于处理日期时间 API 相对糟糕,项目开发往往重度依赖第三方类库,如
Joda-Time
。新的 API可以减轻工具类对第三方库的依赖,但不能替换旧的 API 。
JDK8之前的日期和时间处理 :
java.util.Date
: 承载日期和时间信息。java.util.Calendar
: 对日期和时间进行操作。java.text.DateFormat
: 格式化和分析日期和时间字符串。下文皆以最常见的
SimpleDateFormat
为例说明。
槽点最多的 Java API :
Date
,Calendar
,SimpleDateFormat
所有属性都是可变的。1) 线程不安全 , 2) 重复计算时不能复用,只能新建对象。
Date
,Calendar
操作不人性化,而且Calendar API
是 IBM 捐的,所以设计不一致。1) 月份都是从 0 开始。2)
Date
年是从 1900 开始的。
1 | LocalDate nowDate = LocalDate.now(); |
新增 API
这里这只列举部分常用的 API,其他会在功能性中补充。
涉及 | 说明 |
---|---|
LocalDate |
LocalDate 是一个不可变的类,它表示日期,默认格式是 yyyy-MM-dd 。 |
LocalTime |
LocalTime 是一个不可变的类,它的实例代表可读格式的时间,默认格式是 hh:mm:ss.zzz 。 |
LocalDateTime |
LocalDateTime 是一个不可变的类,它表示一组日期-时间,默认格式是 yyyy-MM-ddTHH-mm-ss.zzz 。 |
LocalDate
1 | /** |
LocalTime
1 | /** |
LocalDateTime
1 | /** |
格式化和解析
JDK8 新增了 java.time.format.DateTimeFormatter
类,负责提供格式化信息。
1 |
|
处理和分析
新的 API 中内置了许多工具方法,可以分析和处理日期和时间。
注意 :这些类都是不可变动的,处理后是返回一个新的对象。
1 |
|
支持旧 API
JDK8 新增了一个 java.time.Instant
类,借助它可以完成 旧API
和 新API
的相互转换。
1 |
|
1 |
|