Java11及Java17新特性体验总结

目前工作中用的版本还是JAVA8,现在JAVA版本发布太快,目前已经有11和17两个LTS版本。从JDK1.8开始,Oracle的JDK 付费政策 就一直被人诟病,但是从17开始,Oracle宣布推出JDK免费服务。并且Spring 已经宣布计划在22年4季度发布的Spring Framework 6 和Spring Boot 3要求的最低版本就是17,所以是时候可以开始尝试一下新版本了。

PS:先更新一下,22年9月发布的JDK19中,终于推出了重磅新特性:虚拟线程。虽然19还不是LTS版本,并且虚拟线程也还处于预览阶段,但是还是非常期待在下个正式版本中能够见到。先简单补一下19的新特性吧,详细体验找时间在补上…….()

首先看下JAVA8以后几个主要版本发布时间以及主要特性。

版本 发布时间 主要特性
JAVA SE 8 (LTS) 2014 年 3 月 lambada表达式
函数式接口
方法引用
默认方法
Stream API 对元素流进行函数式操作
Optional 解决NullPointerException
Date Time API
重复注解 @Repeatable
Base64
使用元空间Metaspace代替持久代(PermGen space)
JAVA 11 (LTS) 2018 年 9 月 增加一些符串处理方法
用于 Lambda 参数的局部变量语法
Http Client重写,支持HTTP/1.1和HTTP/2 ,也支持 websockets
可运行单一Java源码文件,如:java Test.java
ZGC:可伸缩低延迟垃圾收集器
支持 TLS 1.3 协议
JAVA 17 (LTS) 2021 年 9 月 Free Java License
JDK 17 将取代 JDK 11 成为下一个长期支持版本
Spring 6 和 Spring Boot 3需要JDK17
移除实验性的 AOT 和 JIT 编译器
恢复始终执行严格模式 (Always-Strict) 的浮点定义
JAVA 19 2022年9月 Record 模式匹配 (预览)
虚拟线程 (预览)
Switch 模式匹配(第三次预览)
Linux/RISC-V Port
Vector API (四次孵化)
外部函数和内存 API (预览)
结构化并发(孵化)
JAVA 20 2022年3月 Record Patterns(记录模式)(预览)
Linux/RISC-V Port
Foreign Function & Memory API(外部函数和内存 API)(预览)
Virtual Threads(虚拟线程)(预览)
Vector(向量)API(第四次孵化)
Pattern Matching for switch(switch 模式匹配)
Structured Concurrency(结构化并发)(孵化)

从JAVA9开始,为了更快地迭代,以及跟进社区反馈,Oracle的开始使用新的发行节奏,半年一个小版本,每三年一个大版本,并且承诺不会跳票,以免出现类似JAVA9那样多次延迟发布的尴尬局面。最近一个LTS版本是2021 年 9 月, 发布的 JAVA 17。

虽然目前大部分公司日常开发使用的还是JAVA8,不过还是需要多了解一下新版本的特性。首先在新的版本发布模式下,大多数变更首先需要经过“预览”阶段,也就是说它们被添加到一个版本中,但还没有完成。人们可以尝试使用它们,但不建议将其用在生产环境中。接下来代码示例中使用的特性都是已经正式添加到该版本,并且已经过了预览阶段。

JAVA11新特性简单使用

1.String API

字符串可能是是日常开发中最常用的一个类,String类的方法使用频率也非常高。JAVA11新增了一系列实用的API

  1. isBlank判空

    1
    2
    3
    4
    String s = "   ";
    boolean blank = s.isBlank();
    //输出true
    System.out.println(blank);
  2. lines()按行分割获取stream流

    1
    2
    3
    4
    5
    6
    7
    String s2 = "第一行\n第二行\n第三行";
    Stream<String> lines = s2.lines();
    lines.forEach(System.out::println);
    //输出
    //第一行
    //第二行
    //第三行
  3. strip()去除空白字符。trim 只能去除半角空格,而 strip去除各种空白符

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    String s3 = " aaacccddd ";
    //去除前后各种空白字符(包括全角半角)
    System.out.println("==="+s3.strip()+"===");
    //去除前面各种空白字符(包括全角半角)
    System.out.println("==="+s3.stripLeading()+"===");
    //去除后面各种空白字符(包括全角半角)
    System.out.println("==="+s3.stripTrailing()+"===");
    //输出
    //===aaacccddd===
    //===aaacccddd ===
    //=== aaacccddd===
  4. repeat()复制字符串

    1
    2
    3
    4
    5
    String s4 = "你好!";
    String repeat = s4.repeat(4);
    System.out.println(repeat);
    //输出
    //你好!你好!你好!你好!
2. 文件操作API

JAVA.nio.file.Files新增方法,读写文件更加简单

1
2
3
4
5
6
7
8
//创建临时文件,写入内容
Path path = Files.writeString(Files.createTempFile("test", ".txt"), "JAVA从入门到放弃");
System.out.println(path);
//读取临时文件
String s = Files.readString(path);
System.out.println(s);
//输出
//JAVA从入门到放弃
3. 更好用的Http Client API

从JAVA9的jdk.incubator.httpclient模块迁移到JAVA.net.http模块,包名由jdk.incubator.http改为JAVA.net.http。这是一个流畅、易于使用的API,完全支持HTTP/1.1和HTTP/2,可以同步或者异步处理响应。可以不需要再依赖第三方Http Client

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
HttpClient client = HttpClient.newBuilder().
//connect timeout
connectTimeout(Duration.ofSeconds(5))
.build();
HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create("https://www.baidu.com"))
//read timeout
.timeout(Duration.ofSeconds(5))
.build();
// 异步
client.sendAsync(request, HttpResponse.BodyHandlers.ofString())
.thenApply(HttpResponse::body)
.thenAccept(System.out::println)
.join();

// 同步
HttpResponse<String> response = client.send(request, HttpResponse.BodyHandlers.ofString());
System.out.println(response.body());
4.其他部分特性
  • 局部变量类型推断。JAVA 11 支持在lambda表达式参数中使用局部变量语法(var关键字)。我们可以利用此特性将修饰符应用于局部变量

  • 直接运行 JAVA 文件。省略JAVAc,直接通过命令执行.JAVA文件

  • 推出的一款新的低延迟垃圾回收器 —— ZGC

  • 新的实验性垃圾回收器 —Epsilon。Epsilon 垃圾回收器不执行任何垃圾回收工作,适用于模拟内存不足错误的场景。有一些特定的用例可能会有用:

    • 性能测试
    • 内存压力测试
    • VM 接口测试
    • 存活极短的任务

    要启用它,添加 -XX:+UnlockExperimentalVMOptions -XX:+UseEpsilonGC 参数。

  • 飞行记录器(Flight Recorder)**飞行记录器 (JFR) 之前是 Oracle JDK 中的一个商用产品,现已在Open JDK中开源**。JFR 是一个性能分析工具,我们可以使用它从正在运行的 JAVA 应用程序中收集诊断和分析数据。要开启一个时长为 120 秒的 JFR 记录,我们可以使用以下参数:

    1
    -XX:StartFlightRecording=duration=120s,settings=profile,filename=JAVA-demo-app.jfr

    JFR性能开销通常低于 1%,因此可以将它用于生产环境。记录的数据保存在.jfr文件中。为了分析和可视化数据,我们还需要使用另一款工具 —— JDK Mission Control (JMC) 。

  • 还有一些重要的变化:

    • 实现了新的 ChaCha20 和 ChaCha20-Poly1305 加密算法取代不安全的RC4。
    • Support for cryptographic key agreement with Curve25519 and Curve448 replace the existing ECDH scheme
    • 升级TLS版本到1.3,提升了安全性和性能
    • 支持 Unicode 10, 带来了更多的字符、符号和表情符号

JAVA17新特性简单使用

注意,下边的新特性包含在12-17中预览并且发布的特性。

1 文本快

在支持文本块之前,相信都对Json等字符串转义、换行感到头疼,虽然大多数IDE支持复制后自动转义,不过大量的转义拼接导致可读性极差。

文本块在 JAVA 13 中预览发布,并正式添加到 JAVA 15 中,它可以简化多行字符串的写法,支持换行,并在不需要转义字符的情况下保持缩进。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
package com.test.JAVA17;

public class TextBlockTest {
public static void main(String[] args) {
//支持文本块之前
String jsonBefore = "{\n" +
" \"id\":1,\n" +
" \"name\":张三,\n" +
" \"age\":20\n" +
" } ";
System.out.println(jsonBefore);
//要使用一个文本块,只需要这样,大大提高可读性
String json = """
{
"id":1,
"name":张三,
"age":20
}
""";
System.out.println(json);
}
}

2.增强Switch

switch表达式在新的版本中不断在增强功能,“万物皆可Switch“

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
package com.test.JAVA17;

public class SwitchCaseTest {
public static void main(String[] args) {
// Object obj = 1;
//Object obj = "1";
// Object obj = null;
Object obj = 1L;
//跟我们熟悉的版本最明显的区别是没有了 break 语句。
switch (obj) {
case null -> {
System.out.println("obj is null ");
throw new NullPointerException();
}
case String s -> System.out.println("String: " + s);
case Integer i -> System.out.println("Integer");
default -> System.out.println("default");
}
}
}

相信绝大多数人都碰到过这种情况:忘记在 switch 里添加 break 语句,只有当代码在运行之后,并且发生错误之后才能发现。。。

switch 表达式通过一种有趣的方式修复了这个问题,只需要用逗号隔开同一个代码块里所有的值。不需要使用 break !它会替你处理好! 省略掉break,也让JAVA 语法更加简洁。

switch 表达式还新增了 yield 关键字。如果一个 case 进入了一个代码块,yield 将被作为 switch 表达式的返回语句。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
package com.test.JAVA17;

public class SwitchCaseTest {
public static void main(String[] args) {
Object obj = 1;
//Object obj = "1";
// Object obj = null;
// Object obj = 1L;
Object result = switch (obj) {
case null -> {
yield null;
}
case String s -> s;
case Integer i -> i.toString();
default -> "default";
};
System.out.println(result);
}
}
3. 封印类

新增关键字sealed。sealed修饰的类和接口限制其他的类或者接口的扩展和实现。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
package com.test.JAVA17;

/**
* 封印类
*/
public class SealedTest {
//在接口或者抽象类前边添加 sealed 关键字的,并且需要添加了一个允许扩展这个类或实现这个接口的类的清单。
sealed interface Pets permits Cat,Dog {
void say();
}
//Cat类必须实现Pets接口,且必须为final
static final class Cat implements Pets {
@Override
public void say() {
System.out.println("喵喵喵");
}
}
static final class Dog implements Pets {
@Override
public void say() {
System.out.println("汪汪汪");
}
}
//编译报错 Pig is not allowed in the sealed hierarchy。不能实现被sealed修饰且未指定的类
static final class Pig implements Pets{
@Override
public void say() {
System.out.println("哼哼哼");
}
}
public static void main(String[] args) {
Pets pets = new Cat();
pets.say();
}
}

4. record 类

record类在 JAVA 14 加入预览,并正式添加到 JAVA 16 中,替代传统的POJO 类。会自动实现equals()和 hashcode()方法会自动实现,toString()将返回这个类实例包含的所有字段的值。相对传统的POJO类的,让代码更加简洁。另外需要注意record 类是 final 和不可变的,属性也是final的。意味着类实例一旦被创建,它的字段就不能被修改。可以在 record 类中声明方法,包括非静态方法和静态方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
package com.test.JAVA17;

public class RecordTest {
record Book(String name, String author) {
//可以有多个构造方法
public Book() {
this("", "");
}
//可以有非静态方法
public String repeatName() {
return name.repeat(2);
}
//可以有静态方法
public static String desc(Book book) {
return book.author() + "作者是" + book.name();
}
}

public static void main(String[] args) {
Book book = new Book("《三体》", "刘慈欣");
System.out.println(book.name() + ":" + book.author());
System.out.println(book.name + ":" + book.author);
System.out.println(book.repeatName());
System.out.println(Book.desc(book));
Book bookDefault = new Book();
System.out.println(bookDefault.name() + ":" + bookDefault.author());
}

}

5. Instanceof模式匹配

模式匹配是 JAVA 消除冗长语法的路上的另一个举措。模式匹配在 JAVA 14加入预览,并正式添加到 JAVA 16 中,它可以在 instanceof 满足后省掉不必要的类型转换。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
package com.test.JAVA17;

public class InstanceOfTest {
public static void main(String[] args) {
travel(new Plane());
//
}

public static void travel(Object vehicle) {
//以前的写法
if (vehicle instanceof Plane) {
((Plane) vehicle).fly();
} else if (vehicle instanceof Car) {
((Car) vehicle).drive();
}
//JAVA17的写法,看起来更加的简洁
if (vehicle instanceof Plane plane) {
plane.fly();
} else if (vehicle instanceof Car car) {
car.drive();
}
}

static class Plane {
public void fly() {
System.out.println("坐飞机");
}
}

static class Car {
public void drive() {
System.out.println("开车");
}
}
}

6. 优化的空指针异常可读性

优化空指针异常](https://openjdk.JAVA.net/jeps/358)在 JAVA 14 中正式发布,提高了空指针异常(NullPointerException,简称 NPE)的可读性,可以打印出在抛出异常位置所调用的方法的名称和空变量的名称。例如,如果你调用 a.b.getName(),而 b 为空,那么异常的堆栈跟踪信息会告诉你调用 getName()失败,因为 b 是空的。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public static void main(String[] args) {
System.out.println(toInt(1.00));
System.out.println(toInt(null));
}

public static int toInt(Double d1) {
return d1.intValue();
}
//异常的堆栈跟踪信息会告诉你调用 intValue()失败,因为 d1 是空的。
Exception in thread "main" JAVA.lang.NullPointerException: Cannot invoke "JAVA.lang.Double.intValue()" because "d1" is null
at com.test.JAVA17.NullPointerExceptionTest.toInt(NullPointerExceptionTest.JAVA:13)
at com.test.JAVA17.NullPointerExceptionTest.main(NullPointerExceptionTest.JAVA:9)
//11的异常堆栈跟踪信息
Exception in thread "main" JAVA.lang.NullPointerException
at com.test.JAVA11.NullPointerExceptionTest.toInt(NullPointerExceptionTest.JAVA:11)
at com.test.JAVA11.NullPointerExceptionTest.main(NullPointerExceptionTest.JAVA:7)

7.增强的伪随机数生成器

增强的伪随机数生成器。将为伪随机数生成器 (PRNG) 提供新的接口类型和实现,包括可跳转的 PRNG 和额外的一类可拆分 PRNG 算法 (LXM)。新接口RandomGenerator将为所有现有的和新的 PRNG 提供统一的 API。将提供四个专门的 RandomGenerator 接口。推动该计划的重点是 JAVA 伪随机数生成领域的多个改进领域。这项工作不需要提供许多其他 PRNG 算法的实现。但是已经添加了一些常用算法,这些算法已经广泛部署在其他编程语言环境中。该计划的目标包括:

  • 使在应用程序中交替使用各种 PRNG 算法变得更容易。
  • 改进了对基于流的编程的支持,提供了 PRNG 对象流。
  • 消除现有 PRNG 类中的代码重复。
  • 保留类的现有行为JAVA.util.Random。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
package com.test.JAVA17;

import JAVA.util.random.RandomGenerator;
import JAVA.util.random.RandomGeneratorFactory;

public class RandomGeneratorTest {
public static void main(String[] args) {
RandomGeneratorFactory.all().forEach(r -> {
//输出全部实现
System.out.println(r.name());
//L32X64MixRandom
//L128X128MixRandom
//L64X128MixRandom
//SecureRandom
//L128X1024MixRandom
//L64X128StarStarRandom
//Xoshiro256PlusPlus
//L64X256MixRandom
//Random
//Xoroshiro128PlusPlus
//L128X256MixRandom
//SplittableRandom
//L64X1024MixRandom
});
//
RandomGeneratorFactory<RandomGenerator> l128X256MixRandom = RandomGeneratorFactory.of("L128X256MixRandom");
// 使用时间戳作为随机数种子
RandomGenerator randomGenerator = l128X256MixRandom.create(System.currentTimeMillis());
for (int i = 0; i < 5; i++) {
System.out.println(randomGenerator.nextInt(10));
}
}
}

8.其它新特性
  • 始终严格的浮点语义

    在 JAVA SE 1.2 之前,所有的浮点计算都是严格的,但是以当初的情况来看,过于严格的浮点计算在当初流行的 x86 架构和 x87 浮点协议处理器上运行,需要大量的额外的指令开销,所以在 JAVA SE 1.2 开始,需要手动使用关键字 strictfp(strict float point) ,它可以用在类、接口或者方法上,被 strictfp 修饰的部分中的 float 和 double 表达式会进行严格浮点计算。但是随着时代发展,硬件早已发生巨变,当初的问题已经不存在了,所以从 JAVA 17 开始,删除了以前的默认语义,现在严格执行所有浮点操作。关键字strictfp仍然存在,但它没有效果。

  • JEP 410: Remove the Experimental AOT and JIT Compiler

    删除实验性 AOT 和 JIT 编译器,它们几乎没有使用,但需要大量维护工作。该计划要求维护 JAVA 级别的 JVM 编译器接口,以便开发人员可以继续使用外部构建的编译器版本进行 JIT 编译。AOT 编译(jaotc 工具)作为一个实验性特性被整合到JDK 9中。该工具使用Graal 编译器,它本身是用 JAVA 编写的,用于 AOT 编译。这些实验性功能未包含在JDK 16 中由 Oracle 发布的版本,没有人抱怨。根据规定的计划,将删除三个 JDK 模块: jdk.aot(jaotc 工具);internal.vm.compiler,Graal 编译器;和 jdk.internal.vm.compiler.management,Graal MBean。与 AOT 编译相关的 HotSpot 代码也将被删除。

  • JEP 412: Foreign Function & Memory API (Incubator)

    加入一个新的API,允许JAVA程序安全有效地访问JAVA堆之外的外部内存。连续在14,15,16预览,17种发布

  • 与平台无关的矢量 API作为孵化 API集成到JDK 16中,将在 JDK 17 中再次孵化,提供一种机制来表达矢量计算,这些计算在运行时可靠地编译为支持的 CPU 架构上的最佳矢量指令。这比等效的标量计算获得了更好的性能。在 JDK 17 中,向量 API 已针对性能和实现进行了增强,包括在字节向量与布尔数组之间进行转换的增强功能。