【都 Java17 了,还不了解 Java 8 ? 】一文带你深入了解 Java 8 新特性
Java 8 (又称为 jdk 1.8) 是 Java 语言开发的一个主要版本。 Oracle 公司于 2014 年 3 月 18 日发布 Java 8 ,它支持函数式编程,新的 JavaScript 引擎,新的日期 API,新的 Stream API 等。(文章很长,建议点赞收藏)
新特性
以下是 Java 8 新增的部分特性,更多新特性了解请详细参考:What's New in JDK 8
• Lambda 表达式• 方法引用• 函数式接口• 默认方法• Stream• Optional 类• Nashorn, JavaScript 引擎• Date/Time API 新的日期时间 API• Base64• 新工具 − 新的编译工具,如:Nashorn 引擎 jjs、 类依赖分析器 jdeps。
一、Lambda 表达式
Lambda 表达式(也可称为闭包),它是推动 Java 8 发布的最重要新特性。Lambda 表达式允许把函数作为一个方法的参数
,可以取代大部分的匿名内部类,写出更优雅的 Java 代码,尤其在集合的遍历和其他集合操作中,可以极大地优化代码结构。
什么是 Lambda 表达式
先来看看 Lambda 表达式的官方解释:Lambda 表达式(lambda expression)是一个匿名函数,Lambda 表达式基于数学中的λ演算得名,直接对应于其中的 lambda 抽象(lambda abstraction),是一个匿名函数
,即没有函数名的函数。
这样的解释还是让人摸不着头脑,那我们接着往下看。
首先介绍, Lambda 表达式的语法格式:() -> {}
前面我们说了 Lambda 表达式可以取代大部分的匿名内部类,举个例子:
运行结果:param:{a=1,b=2}
接着,我们将匿名类实现替换为 Lambda 表达式
运行结果与之前相同,可以看到,Lambda 表达式可以来定义行内执行的方法类型接口,免去了使用匿名方法的麻烦。简单来说,在 Java 中可以将 Lambda 表达式看成一个接口的实现,但并不是所有的接口都可以使用 Lambda 表达式来实现。
Lambda 规定接口中只能有一个需要被实现的方法,即函数式接口
,不是规定接口中只能有一个方法。
闭包问题
lambda 表达式只能引用标记了 final
的外层局部变量,这就是说不能在 lambda 内部修改定义在域外的局部变量,否则会编译错误。
这里 c 没有标识为final
,但是没有被后续代码修改,所以在编译期间虚拟机会帮我们加上 final 修饰关键字(即隐性的具有 final 的语义)
修改代码,出现错误java: 从lambda 表达式引用的本地变量必须是最终变量或实际上的最终变量
二、方法引用
方法引用通过方法的名字来指向一个方法,在使用 Lambda 表达式时,有时候我们不是必须要自己重写某个匿名内部类的方法,而是可以利用 Lambda 表达式的接口快速指向一个已经被实现的方法。
Java 中 4 种不同方法的引用:• 构造器引用 ClassName::new• 静态方法引用 Class::static_method• 特定类的任意对象的方法引用 Class::method• 特定对象的方法引用 instance::method
代码示例:
运行结果:
三、函数式接口
上面提到接口中只有一个需要被实现的方法的接口,叫做函数式接口。
函数式接口需要用@FunctionalInterface
注解修饰,要求接口中的抽象方法只有一个,但可以有多个非抽象方法。
函数式接口实例
函数式接口可以对现有的函数友好地支持 lambda。比如常用的Comparator
或者Consumer
接口。
比如常见的集合内元素的排序
在以前我们若要为集合内的元素排序,就必须调用 sort 方法,传入比较器匿名内部类重写 compare 方法,我们现在可以使用 lambda 表达式来简化代码。
更多函数式接口
JDK 1.8 之前已有的函数式接口:
• java.lang.Runnable
• java.util.concurrent.Callable
• java.security.PrivilegedAction
• java.util.Comparator
• java.io.FileFilter
• java.nio.file.PathMatcher
• java.lang.reflect.InvocationHandler
• java.beans.PropertyChangeListener
• java.awt.event.ActionListener
• javax.swing.event.ChangeListener
JDK 1.8 新增加的函数接口:
• java.util.function
java.util.function 它包含了很多类,用来支持 Java 的 函数式编程,详细的函数式接口请查看源码。
四、默认方法
简单说,默认方法就是接口可以有实现方法,而且不需要实现类去实现其方法。用法:只需在方法名前面加个 default
关键字即可实现默认方法。
相同的默认方法
当一个类实现多个接口,而且这些接口存在相同的默认方法,会发生什么情况呢?
运行结果:
当出现这种接口冲突,一般有两种解决方案 覆盖重写接口的默认方法
、使用 super 来调用指定接口的默认方法
静态默认方法
Java 8 中接口是可以声明静态方法(并且可以提供实现)的。例如:
为什么要有这个特性
说了这么多,那么问题来了,为什么要新增这个特性?
新增默认方法是为了解决接口的修改与现有的实现不兼容的问题。我们都知道接口是面向抽象而不是面向具体编程的,所以当需要修改接口时,就需要修改全部实现改接口的类。所以对于以前发布的版本,是做不到在修改接口的同时不影响已有的实现。
五、Stream
Java 8 API 添加了一个新的抽象称为流 Stream,可以让你以一种声明的方式
处理数据。
语法糖 Stream 以一种类似用 SQL 语句从数据库查询数据的直观方式来提供一种对 Java 集合运算和表达的高阶抽象
。简单来说流是 Java 8 中对 Collection 对象功能的加强。
流操作
【数据集合】 ->【数据源】 - > 【转换(聚合)操作】 ->【终点操作】
• Intermediate(转换操作):中间操作都会返回流对象本身。就是说,仅仅调用到这类方法,并没有真正开始流的遍历。多次的转换操作只会在遇到终点操作之后,才会依次执行。
转换操作:map (mapToInt, flatMap 等)、 filter、 distinct、 sorted、 peek、 limit、 skip、 parallel...
• Terminal(终点操作):一个流只能有一个 terminal 操作,当这个操作执行后,流就被使用“光”了,无法再被操作。所以这必定是流的最后一个操作。Terminal 操作的执行,才会真正开始流的遍历,并且会生成一个结果,或者一个 side effect。
终点操作:forEach、 forEachOrdered、 toArray、 reduce、 collect、 min、 max、 count、 anyMatch、 allMatch、 noneMatch、 findFirst、 findAny...
• Short-Circuiting(短路操作):短路操作其实和终点操作也是一样的,可能不再返回一个流,或是返回一个被截取过的流。比如 anyMatch 方法,通过 Predicate<T>接口返回了一个真值。由于流 Stream 在理论上是无限大的,短路操作被用以对流进行截取,把无限的变成有限的流,比如 limit 方法,可以限制获取数据源的数量。
短路操作:anyMatch、 allMatch、 noneMatch、 findFirst、 findAny、 limit...
外部迭代与内部迭代
这里值得一提的是以前对集合遍历都是通过 Iterator 或者 for-each 的方式,显式的在集合外部进行迭代, 这叫做外部迭代
。与以前的 Collection 操作不同,流操作提供了内部迭代
的方式, 通过访问者模式(Visitor)实现。
那什么是外部迭代和内部迭代呢?举个简单的列子:
比如你请人打扫房间,但有觉得不放心,于是你觉得现场指示工人先擦桌子,再拖地,最后洗碗...直到打扫完毕,这就是所谓的外部迭代,即显示地取出元素进行处理。后来你和清洁工人熟悉之后,你只需要和她说把房间打扫干净,清洁工人自己选择先做什么,再做什么,你等着接收成果就行了。这就是内部迭代。
生成流
顶层集合类 Collection 添加了两个方法:stream()
、parallelStream()
。• stream() − 为集合创建串行流。• parallelStream() − 为集合创建并行流。
开启流计算时根据操作的数据量选择调用 stream()或者 parallelStream()
使用实例
forEach
Stream 提供了新的方法 'forEach' 来迭代流中的每个数据。
map
map 方法用于映射每个元素到对应的结,以下代码片段使用 map 输出了元素对应的大写
filter
filter 方法用于通过设置的条件过滤出元素。把除了abc
的字符串过滤出来
limit
limit 方法用于获取指定数量的流。 打印 3 条数据
流操作简单说明
• filter
:用于过滤出满足条件的元素• distinct
:去重,需要重写 equals()和 hashCode()• sorted
:对元素进行排序• limit
:返回前 n 个元素• skip
:去掉前 n 个元素• map
:方法用于映射每个元素对应的结果• flapMap
:将流中的每一个元素 T 映射成为一个流,再把每一个流连接成一个流• anyMatch
:是否存在任意一个元素满足条件(返回布尔值)• allMatch
:是否所有元素都满足条件(返回布尔值)• noneMatch
:是否所有元素都不满足条件(返回布尔值)• findAny
:找到其中一个元素 (使用 stream() 时找到的是第一个元素;使用 parallelStream() 并行时找到的是其中一个元素)• findFirst
:找到第一个元素• reduce
:用于组合流中的元素,如求和,求积,求最大值等• count
:返回流中元素个数,结果为 long 类型• collect
:收集方法,我们很常用的是 collect(toList()),当然还有 collect(toSet()) 等,参数是一个收集器接口
Stream 流式计算的使用
说了这么多,那么流使用好处以及对性能的影响如何呢?
Stream API 可以极大提高 Java 程序员的生产力,让程序员写出高效率、干净、简洁的代码。
通过实际示例对比了常规的集合类的过滤、封装、统计操作,几百的小数据量操作,常规外部迭代更快;数据量再大一点,stream()串行的流式计算会更快;上万级别的数据量后,parallelStream()并行流式计算会更快。
六、Optional 类
相信大家在编码中最常遇见的就是空指针异常,而 Optional 类的引入就是为了很好地解决空指针异常。
Optional
是个容器:它可以保存类型 T 的值,或者仅仅保存 null。Optional 提供很多有用的方法,这样我们就不用显式进行空值检测。
常用类方法
简单举例
orElse
存在则返回aa
,不存在则返回bb
七、日期时间 API
旧版日期时间 API 问题:• 非线程安全
: java.util.Date 是非线程安全的,所有的日期类都是可变的,这是 Java 日期类最大的问题之一。• 日期/时间类的定义并不一致
:在 java.util 和 java.sql 的包中都有日期类• 时区处理麻烦
:日期类并不提供国际化,没有时区支持
Java 8 在 java.time
包下提供了很多新的 API:• Local(本地)
:简化了日期时间的处理,没有时区的问题。• Zoned(时区
:通过制定的时区处理日期时间。
新的 java.time 包涵盖了所有处理日期,时间,日期/时间,时区,时刻(instants),过程(during)与时钟(clock)的操作。
使用时区的日期时间 API
时区使用 ZoneId
来表示,使用静态方法of
来获取时区
本地化日期时间 API
LocalDate、LocalTime 、LocalDateTime 都是用于处理日期时间的 API,在处理日期时间时可以不用强制性指定时区
自定义格式使用DateTimeFormatter
,它是不可变的(线程安全)
八、Base64
在 Java 8 中,Base64 编码已经成为 Java 类库的标准。至于它的使用则十分简单,来看个例子:
输出结果为:
SmF2YTgtQmFzZTY0Java8-Base64
此外,Base64 工具类还提供了 URL、MIME 编解码器
九、Nashorn JavaScript 引擎
Nashorn 是一个 javascript 引擎,使得 JavaScript 代码可以在 Java 中执行,如下:
jjs
jjs 是个基于 Nashorn 引擎的命令行工具。它接受一些 JavaScript 源代码为参数,并且执行这些源代码。
使用 Nashorn 运行脚本的示例
jjs script.js
在交互模式下运行 Nashorn 的示例
jjsjjs> println("Hello, World!")Hello, World!jjs> quit()
将参数传递给 Nashorn 的示例
$ jjs -- a b cjjs> arguments.join(", ")a, b, cjjs>
值得注意的是:
随着 ECMAScript 语言标准的快速发展,维护 Nashorn 引擎变得越发挑战,因此该引擎将在 Java 中废弃。Java11 将声明弃用 Nashorn JavaScript 脚本引擎,被标注为
@Deprecated(forRemoval=true)
。
十、类依赖分析器 jdeps
jdeps 是一个相当棒的命令行工具,它可以展示包层级和类层级的 Java 类依赖关系,它以.class 文件、目录或者 Jar 文件为输入,然后会把依赖关系输出到控制台。
我们可以利用 jedps 分析下org.springframework.core-3.0.5.RELEASE.jar
,这个命令会输出很多结果,我们仅看下其中的一部分:依赖关系按照包分组,如果在 classpath 上找不到依赖,则显示 not found。
参考资料:Lambda表达式详解Java8 新特性之八---------类依赖分析器:jdeps
创作不易,关注💖、点赞👍、收藏🎉就是对作者最大的鼓励👏,欢迎在下方评论留言🧐
版权声明: 本文为 InfoQ 作者【猫的树】的原创文章。
原文链接:【http://xie.infoq.cn/article/8705e75271f32c3ef1086f616】。文章转载请联系作者。
评论