再见Java 8,请掌握最新LTS

简介

   在Java开发中,Java 8曾经是无可争议的主流,凭借其稳定性和广泛的社区支持,陪伴了无数开发者走过多年辉煌时刻。然而,随着时间的推移,技术不断革新,企业和开发者们逐渐把目光投向了更新的LTS(Long-Term Support )版本:Java 11Java 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
  1. lines():将字符串按行拆分为Stream,方便进行流式处理。
String text = "Hello\nJava 11\nNew Features";
text.lines().forEach(System.out::println);
// 输出:
// Hello
// Java 11
// New Features
  1. strip():去除字符串首尾的空白字符,比trim()更智能,支持Unicode空白字符。
String str = "  Hello  ";
System.out.println(str.strip());  // 输出: "Hello"
  1. 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,从而严格控制了类层次结构。
子类扩展选项:

  1. final:类无法被进一步继承。
  2. sealed:子类继续限制继承,并明确列出允许继承的类。
  3. 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 密集型应用。
​ • 简化代码:开发者不再需要频繁使用复杂的线程池配置或手动管理线程池。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

小夏陌

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值