一文搞定 Java 8 的新特性

Java 8 是Java历史上一个重大的版本更新,发布于2014年3月18日。

JEP 126:Lambda 表达式

Lambda 表达式是 Java 8 新特性中最重要且最显著的一个,为 Java 增加了函数式编程的能力,使得代码变得更加简洁和易读。Lambda 表达式主要用于简化匿名内部类的实现。

Lambda 表达式的基本语法:

rust 复制代码
(parameters) -> expression 或 (parameters) -> { statements; }
  • parameters :是 Lambda表达式的参数列表,可以为空或包含一个或多个参数。
  • -> :是 Lambda 操作符,用于将参数和 Lambda 主体分开。
  • expression :是 Lambda 表达式的返回值,或者在主体中执行的单一表达式。
  • { statements; } :是 Lambda 主体,包含了一系列语句,如果需要执行多个操作,就需要使用这种形式。

它具有如下几个特点:

  1. 无需声明类型:Lambda 表达式不需要声明参数类型,编译器可以自动推断参数类型。
  2. 可选的参数圆括号:当只有一个参数时,可以省略圆括号。但是当参数个数大于一个时,圆括号是必需的。空括号用于表示空参数集。
  3. 可选的大括号:当 Lambda 表达式的主体只包含一个表达式时,可以省略大括号。当表达式需要包含多个语句时,需要使用大括号。
  4. 可选的返回关键字:当 Lambda 表达式主体只有一个表达式,且该表达式会自动返回结果时,可以省略 return 关键字。

更多阅读:Java 8 新特性--- Lambda 表达式

JEP 126:函数式接口

Java 8 引入函数式接口的主要目的是支持函数式编程范式,也就是 Lambda 表达式。在函数式编程语言中,函数被当做一等公民对待,Lambda 表达式的类型是函数,它可以像其他数据类型一样进行传递、赋值和操作。但是在 Java 中,"一切皆对象 "是不可违背的宗旨,所以 Lambda 表达式是对象,而不是函数,他们必须要依附于一类特别的对象类型:函数式接口。所以函数式接口是与Lambda表达式紧密相连的,它为Java添加了一种新的抽象层次,允许将方法作为一等公民对待

函数式接口具有两个特点:

  1. 只包含一个抽象方法:函数式接口只能有一个抽象方法,但可以包含多个默认方法或静态方法。
  2. **@FunctionalInterface**注解标记:该注解不强制,但通常会使用它来标记该接口为函数式接口。这样做可以让编译器检查接口是否符合函数式接口的定义,以避免不必要的错误。

一般来说函数式接口有两个最主要的用途:

  1. 与 Lambda表达式一起使用,为Java带来更加函数式的编程风格。
  2. 用于实现简单的函数策略或行为,如回调、事件处理等。

更多阅读:Java 8 新特性---函数式接口

JEP 179:方法引用

为了提升 Java 编程语言的表达力和可读性,特别是在配合 Lambda 表达式和函数式编程风格,Java 8 引入方法引用。

方法引用实际上是一个简化版的 Lambda 表达式,它允许我们以更简洁的方式引用方法。它有如下几种类型:

  1. 静态方法引用 :使用 类名::静态方法名 的形式。

    • 例如,String::valueOf 相当于 x -> String.valueOf(x)
  2. 实例方法引用(对象的实例方法) :使用 实例对象::实例方法名 的形式。

    • 例如,假设有一个 String 对象 myString,那么 myString::length 相当于 () -> myString.length()
  3. 特定类型的任意对象的实例方法引用 :使用 类名::实例方法名

    • 例如,String::length 相当于 str -> str.length()。这里不是调用特定对象的 length 方法,而是用于任意的 String 对象。
  4. 构造器引用 :使用 类名::new

    • 例如,ArrayList::new 相当于 () -> new ArrayList<>()

更多阅读:Java 8 新特性---方法引用和构造器引用

JEP 150:接口的默认方法

在 Java 8 之前,接口中可以申明方法和变量的,只不过变量必须是 public、static、final 的,方法必须是 public、abstract的。我们知道接口的设计是一项巨大的工作,因为如果我们需要在接口中新增一个方法,需要对它的所有实现类都进行修改,如果它的实现类比较少还可以接受,如果实现类比较多则工作量就比较大了。

为了解决这个问题,Java 8 引入了默认方法,默认方法允许在接口中添加具有默认实现的方法,它使得接口可以包含方法的实现,而不仅仅是抽象方法的定义。

默认方法是接口中带有 default 关键字的非抽象方法。这种方法可以有自己的实现,而不需要子类去覆盖它。

默认方法允许我们向接口添加新方法而不破坏现有的实现。它解决了在 Java 8 之前,向接口添加新方法意味着所有实现该接口的类都必须修改的问题。

更多阅读:Java 8 新特性---接口默认方法和静态方法

JEP 107:Stream API

为了解决 Java 8 之前版本中集合操作的一些限制和不足,提高数据处理的效率和代码的简洁性,Java 8 引入 Stream API,它的引入标志着 Java 对集合操作迎来了的一种全新的处理方式,它在处理集合类时提供了一种更高效、声明式的方法。

Stream API 的核心思想是将数据处理操作以函数式的方式链式连接,以便于执行各种操作,如过滤、映射、排序、归约等,而无需显式编写传统的循环代码。

下面是 Stream API 的一些重要概念和操作:

  1. Stream****(流 ):Stream 是 Java 8 中处理集合的关键抽象概念,它是数据渠道,用于操作数据源所生成的元素序列。这些数据源可以来自集合(Collection)、数组、I/O 操作等等。它具有如下几个特点:

    1. Stream 不会存储数据。
    2. Stream 不会改变源数据对象,它返回一个持有结果的新的 Stream
    3. Stream 操作是延迟执行的,这就意味着他们要等到需要结果的时候才会去执行。
  2. 中间操作 :这些操作允许您在 Stream 上执行一系列的数据处理。常见的中间操作有 filter(过滤)、map(映射)、distinct(去重)、sorted(排序)、limit(截断)、skip(跳过)等。这些操作返回的仍然是一个 Stream。

  3. 终端操作 :终端操作是对流进行最终处理的操作。当调用终端操作时,流将被消费,不能再进行进一步的中间操作。常见的终端操作包括 forEach(遍历元素)、collect(将元素收集到集合中)、reduce(归约操作,如求和、求最大值)、count(计数)等。

  4. 惰性求值:Stream 操作是惰性的,只有在调用终端操作时才会执行中间操作。这可以提高性能,因为只处理需要的数据。

更多阅读:Java 8 新特性---Stream API 对元素流进行函数式操作

Optional 类

Java 8 引入了 Optional 类,这是一个为了解决空指针异常(NullPointerException)而设计的容器类。它可以帮助开发者在编程时更优雅地处理可能为 null 的情况。

更多阅读:Java 8 新特性--- 利用 Optional 解决NullPointerException

JEP 170:新的日期时间 API

作为 Java 开发者你一定直接或者间接使用过 java.util.Datejava.util.Calendarjava.text.SimpleDateFormat 这三个类吧,这三个类是 Java 用于处理日期、日历、日期时间格式化的。由于他们存在一些问题,诸如:

  1. 线程不安全

    1. java.util.Datejava.util.Calendar 线程不安全,这就导致我们在多线程环境使用需要额外注意。
    2. java.text.SimpleDateFormat 也是线程不安全的,这可能导致性能问题和日期格式化错误。而且它的模式字符串容易出错,且不够直观。
  2. 可变性java.util.Date类是可变的,这意味着我们可以随时修改它,如果一不小心就会导致数据不一致问题。

  3. 时区处理困难 :Java 8 版本以前的日期 API 在时区处理上存在问题,例如时区转换和夏令时处理不够灵活和准确。而且时区信息在 Date 对象中存储不明确,这使得正确处理时区变得复杂。

  4. 设计不佳

    1. 日期和日期格式化分布在多个包中。
    2. java.util.Date 的默认日期,年竟然是从 1900 开始,月从 1 开始,日从 1 开始,没有统一性。而且 java.util.Date 类也缺少直接操作日期的相关方法。
    3. 日期和时间处理通常需要大量的样板代码,使得代码变得冗长和难以维护。

基于上述原因,Java 8 重新设计了日期时间 API,以提供更好的性能、可读性和可用性,同时解决了这些问题,使得在 Java 中处理日期和时间变得更加方便和可靠。相比 Java 8 之前的版本,Java 8 版本的日期时间 API 具有如下几个优点:

  1. 不可变性(Immutability) :Java 8的日期时间类(如LocalDateLocalTimeLocalDateTime)都是不可变的,一旦创建就不能被修改。这确保了线程安全,避免了并发问题。
  2. 清晰的API设计 :Java 8 的日期时间 API 采用了更清晰、更一致的设计,相比于以前版本的 DateCalendar 更易于理解和使用。而且它们还提供了丰富的方法来执行日期和时间的各种操作,如加减、比较、格式化等。
  3. 本地化支持:Java 8 的日期时间 API 支持本地化,可以轻松处理不同地区和语言的日期和时间格式。它们能够自动适应不同的时区和夏令时规则。
  4. 新的时区处理 :Java 8引入了 ZoneIdZoneOffset 等新的时区类,使时区处理更加精确和灵活。这有助于解决以前版本中时区处理的问题。
  5. 新的格式化API :Java 8引入了 DateTimeFormatter 类,用于格式化和解析日期和时间,支持自定义格式和本地化。这提供了更强大和灵活的格式化选项。
  6. 更好的性能:Java 8 的日期时间API 比以前的API 性能更佳。

更多阅读: Java 8 新特性---日期时间 API Java 8 新特性---日期时间格式化

JEP 120:重复注解

在 Java 8 之前的版本中,对于一个特定的类型,一个注解在同一个声明上只能使用一次。Java 8 引入了重复注解,它允许对同一个类型的注解在同一声明或类型上多次使用。

工作原理如下:

  1. 定义重复注解 :您需要定义一个注解,并用 @Repeatable 元注解标注它。@Repeatable 接收一个参数,该参数是一个容器注解,用于存储重复注解的实例。
  2. 定义容器注解 :容器注解定义了一个注解数组,用于存放重复注解的多个实例。这个容器注解也需要具有运行时的保留策略(@Retention(RetentionPolicy.RUNTIME))。

更多阅读:Java 8 新特性---重复注解@Repeatable

Base64 编码解码

在 Java 8 之前,我们通常需要依赖于第三方库(如 Apache Commons Codec)或者使用 Java 内部类(如 sun.misc.BASE64Encodersun.misc.BASE64Decoder)来处理 Base64 编解码。但是这些内部类并非 Java 官方的一部分,它们的使用并不推荐,因为它们可能会在未来的版本中发生变化,造成兼容性问题。同时使用非官方或内部 API 可能导致安全漏洞或运行时错误,所以 Java 8 引入一个新的 Base64 编解码 API,它处理 Base64 编码和解码的官方、标准化的方法。

Java 8 中的 Base64 API 包含在 java.util 包中。它提供了以下三种类型的 Base64 编解码器:

  1. 基本型(Basic) :用于处理常规的 Base64 编码和解码。它不对输出进行换行处理,适合于在URLs和文件名中使用。
  2. URL和文件名安全型(URL and Filename Safe) :输出映射到一组 URL 和文件名安全的字符集。它使用 '-' 和 '_' 替换标准 Base64 中的 '+' 和 '/' 字符。
  3. MIME型:用于处理 MIME 类型的数据(例如,邮件)。它在每行生成 76 个字符后插入一个换行符。

更多阅读:Java 8 新特性---全新的、标准的Base 64 API

JEP 104:类型注解

在 Java 8 之前,注解仅限于声明(如类、方法或字段)。这种限制意味着注解的用途在许多编程情景中受到限制,特别是在需要对类型本身(而不仅仅是声明)进行描述时。为了提高注解的能力,Java 8 引入类型注解来增强注解的功能。

该特性扩展了注解的应用范围,允许我们将注解应用于任何使用类型的地方,而不仅仅是声明。包括以下情况:

  • 对象创建(如 new 表达式)
  • 类型转换和强制类型转换
  • 实现(implements)语句
  • 泛型类型参数(如 List<@NonNull String>

更多阅读:Java 8 新特性---类型注解

JEP 101:类型推断优化

在 Java 8 之前,Java 的类型推断主要局限于泛型方法调用的返回类型。这意味着在许多情况下,我们不得不显式指定泛型参数,即使它们可以从上下文中推断出来。这种限制使得代码变得冗长且不够直观,特别是在使用泛型集合和泛型方法时。

为了提高编码效率和可读性,同时简化泛型使用,Java 8 中引入了对类型推断机制的优化,扩大了类型推断的范围,使其能在更多情况下自动推断出类型信息,包括:

  1. Lambda 表达式中的类型推断:在使用 Lambda 表达式时,编译器可以根据上下文推断出参数类型,从而减少了在某些情况下编写显式类型的需求。
  2. 泛型方法调用的改进:在调用泛型方法时,编译器可以更好地推断方法参数、返回类型以及链式调用中间步骤的类型。
  3. 泛型构造器的类型推断:在创建泛型对象时,编译器能够推断出构造器参数的类型。

更多阅读: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 的替代品。主要特点:

  1. 基于 JVM 的执行:Nashorn 是作为 Java 虚拟机的一个原生组件实现的,它直接编译 JavaScript 代码到 Java 字节码。这意味着它可以充分利用 JVM 的性能优化和管理能力。
  2. 高性能:与 Rhino 相比,Nashorn 提供了显著的性能提升,特别是在执行 JavaScript 代码方面。
  3. 与 Java 的深度集成:Nashorn 允许 JavaScript 代码和 Java 代码之间有更紧密的交互。开发者可以在 JavaScript 中方便地调用 Java 类库和对象,反之亦然。
  4. ECMAScript 5.1 支持:Nashorn 支持 ECMAScript 5.1 规范,为开发者提供了一个符合标准的现代 JavaScript 编程环境。

JEP 122:移除Permgen

在 Java 8 之前,JJVM使用永久代(PermGen)的内存区域来存储类的元数据和方法数据。随着时间的推移,这个设计开始显现出一些问题,特别是在应用程序频繁加载和卸载类的场景中,比如在 Java EE 应用服务器和热部署环境中。

永久代有一个固定的大小限制,当类的数量和大小超过这个限制时,就会抛出 OutOfMemoryError: PermGen space 错误。这种设计限制了 Java 的灵活性和可伸缩性。

Java 8 移除永久代并用元空间(Metaspace)的新内存区域来取代它。相比永久代,元空间的具有如下优势:

  1. 基于本地内存:元空间不在 JVM 的堆内存中,而是直接使用本地内存(操作系统的内存)。这意味着它不再受到 Java 堆大小的限制。
  2. 动态调整大小:元空间的大小可以根据应用程序的需求动态调整。这减少了内存溢出的风险,并允许应用更高效地管理内存。
  3. 更好的性能:由于移除了固定大小的限制,元空间可以提供更好的性能,尤其是在大型应用和复杂的部署环境中。
相关推荐
海兰15 分钟前
使用 Spring AI 打造企业级 RAG 知识库第二部分:AI 实战
java·人工智能·spring
历程里程碑32 分钟前
二叉树---二叉树的中序遍历
java·大数据·开发语言·elasticsearch·链表·搜索引擎·lua
小信丶1 小时前
Spring Cloud Stream EnableBinding注解详解:定义、应用场景与示例代码
java·spring boot·后端·spring
无限进步_1 小时前
【C++】验证回文字符串:高效算法详解与优化
java·开发语言·c++·git·算法·github·visual studio
亚历克斯神1 小时前
Spring Cloud 2026 架构演进
java·spring·微服务
七夜zippoe1 小时前
Spring Cloud与Dubbo架构哲学对决
java·spring cloud·架构·dubbo·配置中心
海派程序猿1 小时前
Spring Cloud Config拉取配置过慢导致服务启动延迟的优化技巧
java
阿维的博客日记1 小时前
为什么不逃逸代表不需要锁,JIT会直接删掉锁
java
William Dawson1 小时前
CAS的底层实现
java
ffqws_1 小时前
Spring Boot入门:通过简单的注册功能串联Controller,Service,Mapper。(含有数据库建立,连接,及一些关键注解的讲解)
数据库·spring boot·后端