再见Java 8,请掌握最新LTS
虽然Java 8的官方支持将持续到2030年,但从目前的使用情况来看,Java 8在实际开发中的地位已逐年下降...
简介
在Java开发中,Java 8曾经是无可争议的主流,凭借其稳定性和广泛的社区支持,陪伴了无数开发者走过多年辉煌时刻。然而,随着时间的推移,技术不断革新,企业和开发者们逐渐把目光投向了更新的LTS(Long-Term Support )版本:Java 11、Java 17、以及最新的Java 21。首先我们来看一下各个LTS版本的支持时间表:
上图所示,虽然Java 8的官方支持将持续到2030年,但从目前的使用情况来看,Java 8在实际开发中的地位已逐年下降。根据New Relic在最近发布的《2024 年 Java 生态系统状况报告》,Java 8的市场占有率自2020年以来已经从80%以上急剧下降至不到30%,而Java 17则迅速上升,成为新主流版本:
我们可以明显看出,Java 17不仅在市场占有率上已经超过Java 8,而且随着时间的推移,这一趋势会愈发明显。如果你还抱着“新版仍你发,我用Java 8”的想法,那么我再说一个恶耗:Spring Framework 6.0和Spring Boot 3.0新版最低支持Java 17。也就是说,许多主流框架已经不再为Java 8提供支持。继续使用Java 8不仅意味着你将面临开发环境的兼容性问题,还会带来以下诸多隐患:
1. 性能劣势
新版本Java在性能、垃圾回收机制、内存管理等多个方面都有显著的改进,旧版本在效率上逐渐落后。
2. 语言新特性缺失
Java 11引入了模块化、局部变量类型推断(var),Java 17带来了sealed类、switch模式匹配等特性,而Java 21更是进一步增强了语言的表达能力。坚持使用Java 8意味着你将错过这些让开发更简洁、代码更易维护的现代化工具。
3. 安全性与维护
随着技术的演进,旧版本不再收到安全更新和漏洞修复,意味着持续使用Java 8的项目将面临更多的安全风险。
4. 生态系统支持减少
随着越来越多的框架和库开始对Java 17及更高版本进行优化与支持,Java 8的用户将发现自己处于不被主流技术栈支持的边缘地带。
总而言之,尽管Java 8曾是无数开发者的心头好,但时代已经改变,继续坚持使用它不仅可能阻碍你项目的现代化进程,还会带来性能瓶颈、安全风险、以及与主流框架的不兼容。拥抱Java 17、Java 21等新版本,不仅能让你在开发中得心应手,更能确保你的项目站在技术的最前沿,享受更多现代化工具的便利与效率。下面会按版本时间线,给各位分享新特性。
主要特性
Java 9 (2017 年 9 月发布)
1. 模块化系统 (Project Jigsaw): 引入模块系统(module),允许开发者更好地组织代码,并提供更强的封装和依赖控制。
2. JShell: 一个交互式的命令行工具,允许开发者快速测试和执行 Java 代码,其它语言也有,开发不太用的到。
3. 多版本兼容 JAR 文件: 允许在一个 JAR 文件中包含多个版本的类,以支持不同的 Java 版本。
4. 新集合工厂方法: List.of(), Set.of(), Map.of() 等快速创建不可变集合的方法。
5. 改进的流 API: 新增了takeWhile(), dropWhile(), 和 iterate() 方法。
Java 10 (2018 年 3 月发布)
1. 局部变量类型推断 (var): 引入了 var 关键字,用于局部变量的类型推断,简化了代码编写。
2. G1 垃圾回收器改进: G1 成为默认垃圾回收器,并对 Full GC 进行了改进。
3. 应用类数据共享 (AppCDS): 扩展了类数据共享(CDS)功能,提高应用程序启动时间。
4. Parallel Full GC for G1: 引入并行 Full GC 以提高性能。
Java 11 (2018 年 9 月发布)
1. 长期支持 (LTS) 版本: Java 11 是一个长期支持版本。
2. HTTP Client API: 新的标准 HTTP 客户端 API(从 Java 9 中孵化出来的特性)。
3. 运行 Java 文件: 允许直接使用 java 命令运行 Java 源代码文件,而不需要先编译。
4. 弃用和移除: 移除了 Java EE 和 CORBA 模块。
5. 字符串方法改进: 新增方法如 isBlank(), lines(), strip(), repeat() 等。
Java 12 (2019 年 3 月发布)
1. Switch 表达式(预览特性): switch 可以作为表达式使用,支持 lambda 风格的 case 标签。
2. JVM 常量 API: 提供一个 API 用于处理 JVM 常量池中的常量。
3. G1 垃圾回收器改进: 引入一个可中断的 Full GC。
Java 13 (2019 年 9 月发布)
1. 文本块(预览特性): 多行字符串字面量,使用 “”" 包围。
2. Switch 表达式增强(预览特性): 进一步增强 switch 表达式,支持 yield 关键字。
Java 14 (2020 年 3 月发布)
1. Switch 表达式正式特性: switch 表达式从预览状态成为正式语言特性。
2. 记录(Record)(预览特性): 一种紧凑的类声明方式,自动生成常用方法如 equals(), hashCode(), 和 toString()。
3. 空指针异常消息: 改进了空指针异常的消息,提供更详细的调试信息。
4. 模式匹配 for instanceof (预览特性) : 简化了类型检查和转换。
Java 15 (2020 年 9 月发布)
1. 文本块正式特性: 文本块从预览特性转为正式特性。
2. 隐藏类: 提供了一个 API 用于定义和操作隐藏类,这些类在运行时生成并且不会暴露给应用程序。
3. ZGC 改进: Z 垃圾回收器退出实验状态,变得更加成熟和稳定。
4. Sealed Classes(预览特性): 引入了密封类,可以限制哪些类可以继承或实现某个类或接口。
Java 16 (2021 年 3 月发布)
1. Records 正式特性: Record 类从预览特性转为正式特性。
2. Pattern Matching for instanceof 正式特性: 模式匹配从预览特性转为正式特性。
3. Vector API(孵化器): 提供了一种矢量化操作的 API,可以在 JVM 上高效执行 SIMD 运算。
4. 迁移到 ZGC: 将 ZGC 引入到 macOS 和 Windows 平台。
Java 17 (2021 年 9 月发布)
1. 长期支持 (LTS) 版本: Java 17 是一个长期支持版本。
2. Sealed Classes 正式特性: 密封类从预览特性转为正式特性。
3. 移除和弃用: 移除了 Applets 和一些过时的 API。
4. 新加密算法: 引入了新的一些加密算法如 EdDSA。
5. 增强的伪随机数生成器: 提供新的随机数生成器接口和实现。
Java 18 (2022年3月)
1. UTF-8 默认字符集: java标准库默认字符集改为UTF-8,提升国际化应用的一致性。
2. 简单Web服务器: 引入了一个简单的Web服务器,便于轻量级开发和测试。
Java 19 (2022年9月)
1. 虚拟线程(Project Loom,预览功能): 引入虚拟线程,大幅简化高并发编程模型。
2. 记录模式匹配(预览功能): 记录模式的匹配进一步改进,以支持更多的解构和匹配操作。
Java 20 (2023年3月)
1. 更多的模式匹配和Switch表达式改进: 持续增强和完善模式匹配功能,简化代码结构。
2. 外部函数和内存API: 提供更直接的非Java代码调用和内存管理方式。
Java 21 (2023年9月)
1. 虚拟线程(正式功能): 虚拟线程从预览状态正式发布,显著简化并发编程,优化资源使用。
2. 模式匹配: 更加全面的模式匹配功能,包括针对记录、集合、数组等的匹配。
3. 增强的序列化: 提供更灵活的序列化机制,增强安全性和性能。
从 Java 8 到 Java 21,Java 引入了许多新特性和改进,包括模块化系统、类型推断、文本块、记录、密封类、模式匹配、垃圾回收器改进、虚拟线程、记录模式匹配、增强的序列化等。每个版本都在增强开发体验、提高性能和安全性方面进行了优化。
重要特性详解
模块化系统 (Project Jigsaw)
模块化系统是Java 9引入的一项重大更新,也是Java平台自诞生以来最深远的变革之一。这一系统的核心目标是解决Java在大规模应用中常见的依赖管理混乱、类库冲突和封装不完善等问题。Java 9的模块化系统通过module-info.java文件定义模块,明确模块的依赖关系和公开的接口。以下是模块化系统的几个核心概念:
1. 模块:模块是对类、接口、包、资源等的组织单元。每个模块可以导出部分包供其他模块使用,同时可以声明自己依赖哪些模块。模块使用module-info.java文件来定义。
2. 模块描述符:模块描述符就是module-info.java文件,用来声明一个模块的信息,类似于Java类中的package。它规定了模块的依赖、导出的包,以及服务的使用和提供情况。基本结构如下:
module 模块名 {
requires 依赖的模块名;
exports 导出的包名;
provides 接口名 with 实现类;
uses 接口名;
}
3.模块路径(Module Path):模块路径类似于类路径(ClassPath),它是Java运行时寻找模块的地方。通过模块路径,Java运行时可以找到模块并解析其依赖关系。
定义模块:在Java 9中,每个模块必须有一个module-info.java文件,用来声明模块的依赖、导出的包等信息。我们来看一个简单的模块定义:module-info.java 文件:
module com.example.moduleA {
requires java.sql; // 依赖其他模块
exports com.example.utils; // 导出指定的包供其他模块使用
}
解释:关键词解释如下
• module com.example.moduleA:声明模块名为com.example.moduleA。
• requires java.sql:表明当前模块依赖于java.sql模块。
• exports com.example.utils:表示当前模块导出com.example.utils包,供其他模块访问。
提供服务:在服务提供模块中,使用provides声明:
module com.example.serviceProvider {
provides com.example.ServiceInterface with com.example.ServiceImpl;
}
表示com.example.ServiceImpl是com.example.ServiceInterface接口的实现类。
使用服务:在服务使用模块中,使用uses声明:
module com.example.serviceConsumer {
uses com.example.ServiceInterface;
}
这表示serviceConsumer模块将查找所有提供ServiceInterface实现的模块。
模块化系统的优势
1.更好的封装:模块系统允许我们将模块内部的实现细节完全隐藏起来,只暴露必要的API给外界使用。这样可以避免外部代码意外地依赖内部实现,增加代码的可维护性。
2.精确的依赖管理:通过模块化系统,开发者可以显式声明模块之间的依赖关系,避免了JAR包之间复杂的依赖关系和冲突。同时,编译器在编译时就能检查出依赖关系问题,减少运行时错误。
3.减少Java平台的体积:由于Java 9将JDK本身也模块化了,开发者可以在打包应用时仅包含需要的模块,减少应用程序的体积。例如,一个不需要java.desktop模块的服务器应用可以不再打包这部分冗余代码。
4.更强的安全性:模块系统引入了更细粒度的控制,使得应用程序可以更加严格地控制对外暴露的API,从而减少安全漏洞的可能性。
多版本兼容 JAR 文件
支持在一个 JAR 文件中包含多个版本的类,以便可以针对不同的 Java 运行环境进行优化。这种多版本 JAR 通过在META-INF/versions目录下包含不同版本的类来实现。这对于库开发者非常有用,可以为不同版本的 Java 提供优化后的实现。
例如,可以在META-INF/versions/9目录下提供特定于Java 9的类实现,其他版本的 Java 仍然可以使用默认实现。
新集合工厂方法、改进的流 API
引入了方便的工厂方法来创建不可变集合,比如List.of(), Set.of() 和 Map.of(),使得开发者不再需要使用传统的集合初始化方式。
// 创建不可变 List
List<String> list = List.of("Java", "Python", "JavaScript");
// 创建不可变 Set
Set<String> set = Set.of("Apple", "Banana", "Orange");
// 创建不可变 Map
Map<String, Integer> map = Map.of(
"One", 1,
"Two", 2,
"Three", 3
);
Java 9 对stream进行了增强,添加了takeWhile()、dropWhile()和iterate()等方法,进一步扩展了流操作的灵活性。
• takeWhile():根据给定的条件,从流的开始获取满足条件的元素,直到条件不再成立为止。
• dropWhile():与takeWhile()相反,丢弃从流的开始到不满足条件为止的元素,之后保留其余元素。
• iterate():用来生成无限流,可以接收条件来生成有限流。
List<Integer> numbers = List.of(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);
// takeWhile 示例:获取小于5的元素
numbers.stream()
.takeWhile(n -> n < 5)
.forEach(System.out::println); // 输出:1, 2, 3, 4
// dropWhile 示例:丢弃小于5的元素,保留其余部分
numbers.stream()
.dropWhile(n -> n < 5)
.forEach(System.out::println); // 输出:5, 6, 7, 8, 9, 10
// iterate 示例:生成 0 到 9 的数字
Stream.iterate(0, n -> n < 10, n -> n + 1)
.forEach(System.out::println); // 输出:0, 1, 2, 3, 4, 5, 6, 7, 8, 9
局部变量类型推断 (var)
var关键字简化了局部变量的声明,使代码更加简洁和可读。开发者不再需要显式地声明局部变量的类型,编译器会根据变量的初始值自动推断类型。
var message = "Hello, Java 10!"; // 编译器推断出 message 是 String 类型
var count = 42; // 推断为 int 类型
var list = List.of(1, 2, 3); // 推断为 List<Integer>
尽管使用了var,变量仍然是强类型的,类型推断的结果会在编译时确定,之后不能更改。换句话说,var只是简化了类型的显式声明,类型本身仍然是确定且不可改变的。
注:虽然var在简化代码上提供了诸多便利,但使用时仍然有一些限制:只能用于局部变量、必须有初始值、不能用于null初始化、可读性变弱等问题。
HTTP Client API
Java 11引入了全新的 HTTP Client API,这是Java 9中的孵化特性,经过优化后成为标准API。新的HTTP客户端取代了旧的HttpURLConnection,提供了更简洁、更高效的HTTP通信方式,支持异步非阻塞操作、HTTP/2 和 WebSocket。
• 同步和异步请求:新API可以发送同步请求,也可以使用CompletableFuture进行异步请求。
• HTTP/2 支持:相比于之前版本,新的客户端原生支持HTTP/2协议,大幅提高了网络请求的性能。
• WebSocket 支持:允许Java应用程序进行WebSocket通信,方便与实时应用程序交互。
public class HttpClientDemo {
public static void main(String[] args) throws Exception {
HttpClient client = HttpClient.newBuilder()
.connectTimeout(Duration.ofSeconds(10))
.build();
HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create("https://jsonplaceholder.typicode.com/posts"))
.GET()
.build();
// 异步请求
client.sendAsync(request, HttpResponse.BodyHandlers.ofString())
.thenApply(HttpResponse::body)
.thenAccept(System.out::println)
.join();
}
}
Java运行程序
Java 11允许开发者直接运行 .java 源文件,而不需要手动编译成字节码。这对于简单的脚本、工具和测试场景极为方便,减少了编译的步骤。
以前:
javac HelloWorld.java
java HelloWorld
现在:
java HelloWorld.java
字符串方法改进
Java 11新增了一系列有用的字符串操作方法,使字符串处理更加高效和简洁:
1. isBlank():判断字符串是否为空或者仅包含空白字符。
String str = " ";
System.out.println(str.isBlank()); // 输出: true
- lines():将字符串按行拆分为Stream,方便进行流式处理。
String text = "Hello\nJava 11\nNew Features";
text.lines().forEach(System.out::println);
// 输出:
// Hello
// Java 11
// New Features
- strip():去除字符串首尾的空白字符,比trim()更智能,支持Unicode空白字符。
String str = " Hello ";
System.out.println(str.strip()); // 输出: "Hello"
- repeat(int count):将字符串重复指定次数,生成新的字符串。
String str = "Java";
System.out.println(str.repeat(3)); // 输出: JavaJavaJava
文本块
Java 13引入了文本块这一新特性,允许使用三重引号 (“”") 来定义多行字符串。文本块为处理多行字符串提供了简洁的语法,特别适用于嵌入JSON、HTML、SQL等多行文本,减少了转义字符的使用,并增强了代码的可读性。
主要特性:
• 使用"""包围多行字符串。
• 自动保持换行,减少对转义字符的依赖。
• 通过控制缩进,使代码更加整齐。
String json = """
{
"name": "Java",
"version": 13,
"features": ["Text Blocks", "Switch Expressions"]
}
""";
System.out.println(json);
Switch 表达式增强
Java 13进一步增强了在Java 12中引入的Switch表达式。此版本的Switch表达式不仅可以作为语句,也可以作为表达式返回值。此外,Java 13引入了yield关键字,用于从switch表达式中返回值,而不再依赖break。
主要增强:
• switch可以作为一个表达式使用,并直接返回值。
• yield关键字用于返回值,替代以往的break。
• 支持箭头 (->)符号,更加简洁。
public class SwitchDemo {
public static void main(String[] args) {
var day = "MONDAY";
// 使用 Switch 表达式
String result = switch (day) {
case "MONDAY", "FRIDAY", "SUNDAY" -> "Weekday";
case "TUESDAY" -> {
// 使用 yield 返回值
yield "Office Day";
}
default -> "Invalid day";
};
System.out.println(result); // 输出: Weekday
}
}
记录(Record)
Record 是Java 14引入的一种新的类声明方式,专为不可变的数据载体设计。它允许开发者以极为简洁的方式定义类,自动生成常用的equals()、hashCode()、toString()、访问器等方法。这种紧凑的声明使代码更加简洁,减少了样板代码,尤其适用于数据传输对象(DTO)等场景。
主要特点:
• 简化代码:开发者无需手动编写getter、toString()等方法,减少重复代码。
• 不可变性:Record中的字段是不可变的,保证了数据的安全性。
• 适用于数据传输:特别适合用于DTO或简单的值对象(Value Object)。
public record Person(String name, int age) {}
public class RecordDemo {
public static void main(String[] args) {
Person person = new Person("Alice", 30);
System.out.println(person.name()); // 输出: Alice
System.out.println(person.age()); // 输出: 30
System.out.println(person); // 输出: Person[name=Alice, age=30]
}
}
空指针异常消息
Java 14对空指针异常(NullPointerException,简称NPE)进行了改进。现在,当发生NPE时,异常消息会提供更详细的调试信息,指明具体哪个对象或表达式为null。这使得调试NPE更加直观,减少了寻找问题根源的时间。
改进前:
• 之前的NPE消息仅仅提示null出现在何处,具体哪个表达式为null,开发者需要自行查找。
改进后:
• 新的NPE消息将提供更详细的信息,显示具体哪个变量或表达式导致了null。
// 代码
public class NullPointerDemo {
public static void main(String[] args) {
String str = null;
System.out.println(str.length()); // 将触发新的 NPE 消息
}
}
// 报错
Exception in thread "main" java.lang.NullPointerException: Cannot invoke "String.length()" because "str" is null
模式匹配 for instanceof
Java 14引入了 模式匹配 for instanceof,简化了类型检查和类型转换。在此之前,instanceof只能检查类型,开发者还需要手动进行强制类型转换。而新的模式匹配允许在类型检查的同时直接进行类型转换,减少了样板代码。
改进前:
if (obj instanceof String) {
String s = (String) obj;
System.out.println(s.length());
}
改进后:
if (obj instanceof String s) {
System.out.println(s.length());
}
模式匹配的优势:
• 代码简化:类型检查与转换合为一体,避免了冗余的类型转换语句。
• 提高可读性:代码更加简洁,减少不必要的样板代码,提升了代码的可读性和可维护性。
Sealed Classes 密封类
Java 15 引入了 密封类(Sealed Classes) 作为一种新的类继承控制机制,它允许开发者明确地限制哪些类可以继承或实现某个类或接口。这一特性为Java的类层次结构引入了更多的灵活性,同时提供了更强的安全性和可预测性。密封类能够帮助开发者设计更精确的API,确保只有经过许可的子类或实现类能够扩展基类或接口。
密封类的关键点:
• 使用sealed关键字声明一个密封类或接口。
• 通过permits关键字,明确列出允许继承该类的子类。
• 子类必须是 final(不可扩展的)、non-sealed(取消限制,允许继续继承)、或者sealed(继续限制扩展)的类型。
密封类的优势:
• 控制继承:开发者可以明确指定哪些类可以继承某个密封类,防止滥用继承。
• 提高安全性:避免意外的扩展或实现,特别是在API设计中,增强了代码的健壮性和可预测性。
• 类型检查更强大:编译器可以进行更严格的类型检查,确保所有可能的子类都得到处理。
// 密封类 Shape
public sealed class Shape permits Circle, Rectangle {}
// 允许继承 Shape 的类
public final class Circle extends Shape {}
// 允许继承 Shape 的类
public final class Rectangle extends Shape {}
在这个例子中,Shape 是一个密封类,它只允许 Circle 和 Rectangle 这两个类继承它,其他任何类都不能继承 Shape,从而严格控制了类层次结构。
子类扩展选项:
- final:类无法被进一步继承。
- sealed:子类继续限制继承,并明确列出允许继承的类。
- non-sealed:取消继承限制,允许该类被任何其他类继承。
示例代码:非密封子类
public non-sealed class Triangle extends Shape {
// 该类允许继续被其他类继承
}
密封类提供了更多的设计灵活性,同时让继承关系更加明确,帮助开发者避免不必要的复杂性和维护难题。
虚拟线程
虚拟线程 这一特性旨在大幅简化高并发编程的模型。虚拟线程是轻量级的线程,它们是与操作系统线程分离的,能够以极低的成本创建和管理。这意味着在应用程序中可以轻松创建数以百万计的线程,而不会面临传统线程的性能瓶颈。
主要特点:
• 轻量级:虚拟线程的开销远小于传统线程,因为它们不直接依赖操作系统的线程。
• 高并发支持:虚拟线程允许开发者轻松编写并发程序而不需要复杂的线程池管理。
• 阻塞操作不会占用底层资源:虚拟线程可以处理阻塞的 I/O 操作,允许更自然的编程模型,同时不影响系统性能。
示例代码:
public class VirtualThreadDemo {
public static void main(String[] args) throws InterruptedException {
Thread.startVirtualThread(() -> {
System.out.println("Hello from a virtual thread");
}).join();
}
}
在这个例子中,我们使用了 Thread.startVirtualThread() 来创建一个虚拟线程,它执行与普通线程相同的代码逻辑,但创建和调度的成本更低。
虚拟线程的优势:
• 可扩展性:虚拟线程可以在大规模并发场景中表现优异,特别是 I/O 密集型应用。
• 简化代码:开发者不再需要频繁使用复杂的线程池配置或手动管理线程池。
开放原子开发者工作坊旨在鼓励更多人参与开源活动,与志同道合的开发者们相互交流开发经验、分享开发心得、获取前沿技术趋势。工作坊有多种形式的开发者活动,如meetup、训练营等,主打技术交流,干货满满,真诚地邀请各位开发者共同参与!
更多推荐
所有评论(0)