Java 8 是Java历史上一个重大的版本更新,发布于2014年3月18日。
JEP 126:Lambda 表达式
Lambda 表达式是 Java 8 新特性中最重要且最显著的一个,为 Java 增加了函数式编程的能力,使得代码变得更加简洁和易读。Lambda 表达式主要用于简化匿名内部类的实现。
Lambda 表达式的基本语法:
(parameters) -> expression 或 (parameters) -> { statements; }
parameters
:是 Lambda表达式的参数列表,可以为空或包含一个或多个参数。->
:是 Lambda 操作符,用于将参数和 Lambda 主体分开。expression
:是 Lambda 表达式的返回值,或者在主体中执行的单一表达式。{ statements; }
:是 Lambda 主体,包含了一系列语句,如果需要执行多个操作,就需要使用这种形式。
它具有如下几个特点:
- 无需声明类型:Lambda 表达式不需要声明参数类型,编译器可以自动推断参数类型。
- 可选的参数圆括号:当只有一个参数时,可以省略圆括号。但是当参数个数大于一个时,圆括号是必需的。空括号用于表示空参数集。
- 可选的大括号:当 Lambda 表达式的主体只包含一个表达式时,可以省略大括号。当表达式需要包含多个语句时,需要使用大括号。
- 可选的返回关键字:当 Lambda 表达式主体只有一个表达式,且该表达式会自动返回结果时,可以省略 return 关键字。
JEP 126:函数式接口
Java 8 引入函数式接口的主要目的是支持函数式编程范式,也就是 Lambda 表达式。在函数式编程语言中,函数被当做一等公民对待,Lambda 表达式的类型是函数,它可以像其他数据类型一样进行传递、赋值和操作。但是在 Java 中,“一切皆对象”是不可违背的宗旨,所以 Lambda 表达式是对象,而不是函数,他们必须要依附于一类特别的对象类型:函数式接口。所以函数式接口是与Lambda表达式紧密相连的,它为Java添加了一种新的抽象层次,允许将方法作为一等公民对待。
函数式接口具有两个特点:
- 只包含一个抽象方法:函数式接口只能有一个抽象方法,但可以包含多个默认方法或静态方法。
- 用**
@FunctionalInterface
**注解标记:该注解不强制,但通常会使用它来标记该接口为函数式接口。这样做可以让编译器检查接口是否符合函数式接口的定义,以避免不必要的错误。
一般来说函数式接口有两个最主要的用途:
- 与 Lambda表达式一起使用,为Java带来更加函数式的编程风格。
- 用于实现简单的函数策略或行为,如回调、事件处理等。
更多阅读:Java 8 新特性—函数式接口
JEP 179:方法引用
为了提升 Java 编程语言的表达力和可读性,特别是在配合 Lambda 表达式和函数式编程风格,Java 8 引入方法引用。
方法引用实际上是一个简化版的 Lambda 表达式,它允许我们以更简洁的方式引用方法。它有如下几种类型:
- 静态方法引用:使用
类名::静态方法名
的形式。- 例如,
String::valueOf
相当于x -> String.valueOf(x)
。
- 例如,
- 实例方法引用(对象的实例方法):使用
实例对象::实例方法名
的形式。- 例如,假设有一个
String
对象myString
,那么myString::length
相当于() -> myString.length()
。
- 例如,假设有一个
- 特定类型的任意对象的实例方法引用:使用
类名::实例方法名
。- 例如,
String::length
相当于str -> str.length()
。这里不是调用特定对象的length
方法,而是用于任意的String
对象。
- 例如,
- 构造器引用:使用
类名::new
。- 例如,
ArrayList::new
相当于() -> new ArrayList<>()
。
- 例如,
JEP 150:接口的默认方法
在 Java 8 之前,接口中可以申明方法和变量的,只不过变量必须是 public、static、final 的,方法必须是 public、abstract的。我们知道接口的设计是一项巨大的工作,因为如果我们需要在接口中新增一个方法,需要对它的所有实现类都进行修改,如果它的实现类比较少还可以接受,如果实现类比较多则工作量就比较大了。
为了解决这个问题,Java 8 引入了默认方法,默认方法允许在接口中添加具有默认实现的方法,它使得接口可以包含方法的实现,而不仅仅是抽象方法的定义。
默认方法是接口中带有 default
关键字的非抽象方法。这种方法可以有自己的实现,而不需要子类去覆盖它。
默认方法允许我们向接口添加新方法而不破坏现有的实现。它解决了在 Java 8 之前,向接口添加新方法意味着所有实现该接口的类都必须修改的问题。
JEP 107:Stream API
为了解决 Java 8 之前版本中集合操作的一些限制和不足,提高数据处理的效率和代码的简洁性,Java 8 引入 Stream API,它的引入标志着 Java 对集合操作迎来了的一种全新的处理方式,它在处理集合类时提供了一种更高效、声明式的方法。
Stream API
的核心思想是将数据处理操作以函数式的方式链式连接,以便于执行各种操作,如过滤、映射、排序、归约等,而无需显式编写传统的循环代码。
下面是 Stream API
的一些重要概念和操作:
Stream
(流):Stream
是 Java 8 中处理集合的关键抽象概念,它是数据渠道,用于操作数据源所生成的元素序列。这些数据源可以来自集合(Collection
)、数组、I/O
操作等等。它具有如下几个特点:Stream
不会存储数据。Stream
不会改变源数据对象,它返回一个持有结果的新的Stream
。Stream
操作是延迟执行的,这就意味着他们要等到需要结果的时候才会去执行。
- 中间操作:这些操作允许您在
Stream
上执行一系列的数据处理。常见的中间操作有filter
(过滤)、map
(映射)、distinct
(去重)、sorted
(排序)、limit
(截断)、skip
(跳过)等。这些操作返回的仍然是一个 Stream。 - 终端操作:终端操作是对流进行最终处理的操作。当调用终端操作时,流将被消费,不能再进行进一步的中间操作。常见的终端操作包括
forEach
(遍历元素)、collect
(将元素收集到集合中)、reduce
(归约操作,如求和、求最大值)、count
(计数)等。 - 惰性求值:Stream 操作是惰性的,只有在调用终端操作时才会执行中间操作。这可以提高性能,因为只处理需要的数据。
Optional 类
Java 8 引入了 Optional
类,这是一个为了解决空指针异常(NullPointerException
)而设计的容器类。它可以帮助开发者在编程时更优雅地处理可能为 null
的情况。
JEP 170:新的日期时间 API
作为 Java 开发者你一定直接或者间接使用过 java.util.Date
、java.util.Calendar
、java.text.SimpleDateFormat
这三个类吧,这三个类是 Java 用于处理日期、日历、日期时间格式化的。由于他们存在一些问题,诸如:
- 线程不安全:
java.util.Date
和java.util.Calendar
线程不安全,这就导致我们在多线程环境使用需要额外注意。java.text.SimpleDateFormat
也是线程不安全的,这可能导致性能问题和日期格式化错误。而且它的模式字符串容易出错,且不够直观。
- 可变性:
java.util.Date
类是可变的,这意味着我们可以随时修改它,如果一不小心就会导致数据不一致问题。 - 时区处理困难:Java 8 版本以前的日期 API 在时区处理上存在问题,例如时区转换和夏令时处理不够灵活和准确。而且时区信息在
Date
对象中存储不明确,这使得正确处理时区变得复杂。 - 设计不佳:
- 日期和日期格式化分布在多个包中。
java.util.Date
的默认日期,年竟然是从 1900 开始,月从 1 开始,日从 1 开始,没有统一性。而且java.util.Date
类也缺少直接操作日期的相关方法。- 日期和时间处理通常需要大量的样板代码,使得代码变得冗长和难以维护。
基于上述原因,Java 8 重新设计了日期时间 API,以提供更好的性能、可读性和可用性,同时解决了这些问题,使得在 Java 中处理日期和时间变得更加方便和可靠。相比 Java 8 之前的版本,Java 8 版本的日期时间 API 具有如下几个优点:
- 不可变性(Immutability):Java 8的日期时间类(如
LocalDate
、LocalTime
和LocalDateTime
)都是不可变的,一旦创建就不能被修改。这确保了线程安全,避免了并发问题。 - 清晰的API设计:Java 8 的日期时间 API 采用了更清晰、更一致的设计,相比于以前版本的
Date
和Calendar
更易于理解和使用。而且它们还提供了丰富的方法来执行日期和时间的各种操作,如加减、比较、格式化等。 - 本地化支持:Java 8 的日期时间 API 支持本地化,可以轻松处理不同地区和语言的日期和时间格式。它们能够自动适应不同的时区和夏令时规则。
- 新的时区处理:Java 8引入了
ZoneId
和ZoneOffset
等新的时区类,使时区处理更加精确和灵活。这有助于解决以前版本中时区处理的问题。 - 新的格式化API:Java 8引入了
DateTimeFormatter
类,用于格式化和解析日期和时间,支持自定义格式和本地化。这提供了更强大和灵活的格式化选项。 - 更好的性能:Java 8 的日期时间API 比以前的API 性能更佳。
JEP 120:重复注解
在 Java 8 之前的版本中,对于一个特定的类型,一个注解在同一个声明上只能使用一次。Java 8 引入了重复注解,它允许对同一个类型的注解在同一声明或类型上多次使用。
工作原理如下:
- 定义重复注解:您需要定义一个注解,并用
@Repeatable
元注解标注它。@Repeatable
接收一个参数,该参数是一个容器注解,用于存储重复注解的实例。 - 定义容器注解:容器注解定义了一个注解数组,用于存放重复注解的多个实例。这个容器注解也需要具有运行时的保留策略(
@Retention(RetentionPolicy.RUNTIME)
)。
Base64 编码解码
在 Java 8 之前,我们通常需要依赖于第三方库(如 Apache Commons Codec)或者使用 Java 内部类(如 sun.misc.BASE64Encoder
和 sun.misc.BASE64Decoder
)来处理 Base64 编解码。但是这些内部类并非 Java 官方的一部分,它们的使用并不推荐,因为它们可能会在未来的版本中发生变化,造成兼容性问题。同时使用非官方或内部 API 可能导致安全漏洞或运行时错误,所以 Java 8 引入一个新的 Base64 编解码 API,它处理 Base64 编码和解码的官方、标准化的方法。
Java 8 中的 Base64 API 包含在 java.util
包中。它提供了以下三种类型的 Base64 编解码器:
- 基本型(Basic):用于处理常规的 Base64 编码和解码。它不对输出进行换行处理,适合于在URLs和文件名中使用。
- URL和文件名安全型(URL and Filename Safe):输出映射到一组 URL 和文件名安全的字符集。它使用 '-' 和 '_' 替换标准 Base64 中的 '+' 和 '/' 字符。
- MIME型:用于处理 MIME 类型的数据(例如,邮件)。它在每行生成 76 个字符后插入一个换行符。
JEP 104:类型注解
在 Java 8 之前,注解仅限于声明(如类、方法或字段)。这种限制意味着注解的用途在许多编程情景中受到限制,特别是在需要对类型本身(而不仅仅是声明)进行描述时。为了提高注解的能力,Java 8 引入类型注解来增强注解的功能。
该特性扩展了注解的应用范围,允许我们将注解应用于任何使用类型的地方,而不仅仅是声明。包括以下情况:
- 对象创建(如
new
表达式) - 类型转换和强制类型转换
- 实现(implements)语句
- 泛型类型参数(如
List<@NonNull String>
)
更多阅读:Java 8 新特性—类型注解
JEP 101:类型推断优化
在 Java 8 之前,Java 的类型推断主要局限于泛型方法调用的返回类型。这意味着在许多情况下,我们不得不显式指定泛型参数,即使它们可以从上下文中推断出来。这种限制使得代码变得冗长且不够直观,特别是在使用泛型集合和泛型方法时。
为了提高编码效率和可读性,同时简化泛型使用,Java 8 中引入了对类型推断机制的优化,扩大了类型推断的范围,使其能在更多情况下自动推断出类型信息,包括:
- Lambda 表达式中的类型推断:在使用 Lambda 表达式时,编译器可以根据上下文推断出参数类型,从而减少了在某些情况下编写显式类型的需求。
- 泛型方法调用的改进:在调用泛型方法时,编译器可以更好地推断方法参数、返回类型以及链式调用中间步骤的类型。
- 泛型构造器的类型推断:在创建泛型对象时,编译器能够推断出构造器参数的类型。
更多阅读:Java 8 新特性—类型推断优化
JEP 174:Nashorn JavaScript 引擎
在 Java 8 之前,Java 平台的主要 JavaScript 引擎是 Mozilla 的 Rhino。Rhino 是一个成熟的引擎,但由于其架构和设计年代较早,它在性能和与 Java 的集成方面存在一些限制。随着 JavaScript 在 Web 和服务器端应用中日益重要,需要一个更现代、更高效的 JavaScript 引擎来提供更好的性能和更深度的 Java 集成。因此,Nashorn 引擎被引入作为 Java 平台的一部分。
Nashorn 是一个基于 Java 的 JavaScript 引擎,它完全用 Java 语言编写,并且是 Rhino 的替代品。主要特点:
- 基于 JVM 的执行:Nashorn 是作为 Java 虚拟机的一个原生组件实现的,它直接编译 JavaScript 代码到 Java 字节码。这意味着它可以充分利用 JVM 的性能优化和管理能力。
- 高性能:与 Rhino 相比,Nashorn 提供了显著的性能提升,特别是在执行 JavaScript 代码方面。
- 与 Java 的深度集成:Nashorn 允许 JavaScript 代码和 Java 代码之间有更紧密的交互。开发者可以在 JavaScript 中方便地调用 Java 类库和对象,反之亦然。
- ECMAScript 5.1 支持:Nashorn 支持 ECMAScript 5.1 规范,为开发者提供了一个符合标准的现代 JavaScript 编程环境。
JEP 122:移除Permgen
在 Java 8 之前,JJVM使用永久代(PermGen)的内存区域来存储类的元数据和方法数据。随着时间的推移,这个设计开始显现出一些问题,特别是在应用程序频繁加载和卸载类的场景中,比如在 Java EE 应用服务器和热部署环境中。
永久代有一个固定的大小限制,当类的数量和大小超过这个限制时,就会抛出 OutOfMemoryError: PermGen space
错误。这种设计限制了 Java 的灵活性和可伸缩性。
Java 8 移除永久代并用元空间(Metaspace)的新内存区域来取代它。相比永久代,元空间的具有如下优势:
- 基于本地内存:元空间不在 JVM 的堆内存中,而是直接使用本地内存(操作系统的内存)。这意味着它不再受到 Java 堆大小的限制。
- 动态调整大小:元空间的大小可以根据应用程序的需求动态调整。这减少了内存溢出的风险,并允许应用更高效地管理内存。
- 更好的性能:由于移除了固定大小的限制,元空间可以提供更好的性能,尤其是在大型应用和复杂的部署环境中。
大明哥花了两个月时间终于写完了 Java 8 ~ Java 21 所有的重要特性,整个系列共 63 篇文章,11w+ 字。
现在终于将其整理成了 PDF 版本,同时,大明哥也整理一套目前市面最常见的热点面试题。微信搜[大明哥聊 Java]或扫描下方二维码关注大明哥的原创公众号[大明哥聊 Java] ,回复【Java 新特性】 即可免费领取。