
Java 17已经在2021年9月14号发布。作为继Java 8,Java 11之后的第三个LTS版本,Java 17必将成为升级Java的下一个目标。由于Java发布周期的修改,对大部分项目来说,只有LTS版本才是会考虑使用的版本。Java 17的更新维护至少会到2029年。
如果你想了解Java的版本更新周期,可以参考下面这个视频。

如果你当前使用的是Java 11,那么升级到Java 17之后,可以使用很多新的特性,也有其他需要注意的地方。本文对这些新特性做一下总结。
记录类型是Java原生的创建值对象(Value Object)的方式。可以替代常用的Lombok中的@Value 注解。可以类比Kotlin中的数据类(data class)和Scala中的case class。
记录类型是一组属性的容器。每一个属性称为记录类型的组件。下面代码中的记录类型 GeoLocation 包含了两个组件:lng 和 lat。
public record GeoLocation(double lng, double lat) {
} 在创建记录类型时,只需要声明组件的名称和类型,编译器会自动生成与之相关的方法,包括:
对全部组件进行初始化的构造器。
与组件名称同名的获取组件值的方法。
根据组件值进行比较的 equals 和 hashCode 方法。
输出组件值的 toString 方法。
如果需要对组件的值进行验证或规范化处理,可以添加自定义的构造器来添加相关的逻辑。如果仅需要对部分组件添加自定义处理逻辑,可以使用紧凑形式的构造器。
记录类型可以嵌套,方便表示复杂的对象结构。
记录类型可以添加在方法中,用来保存方法执行中所需的中间结果。
关于记录类型的详细介绍,请参考下面这个视频。

密封类提供了一种方式来限制类的继承。父类可以指定所允许的子类。
密封类的子类可以有三种选择:
声明为 final,禁止继承。
声明为 sealed,继续限制继承。
声明为 non-sealed,恢复会默认的开放继承模式。
这三种选择必须选一个,也只能选一个。
接口也是可以密封的。
下面的代码是密封类的使用示例。
public abstract sealed class Shape {
final class Circle extends Shape {
}
sealed class Rectangle extends Shape {
final class Square extends Rectangle {
}
}
non-sealed class FreeFormShape extends Shape {
}
} 关于密封类的详细介绍,请参考下面的这个视频。

switch 可以作为表达式来使用,有自己的值。
switch 可以使用箭头标签。使用箭头标签的 case 分支的执行,不会自动转到下一个分支,相当于添加了 break。
如果 switch 表达式的 case 分支是一个代码块,需要使用 yield 来提供表达式的值。
下面代码是 switch 表达式的使用示例。
public class SwitchExpression {
public String formatGifts(int number) {
return switch (number) {
case 0 -> "no gifts";
case 1 -> "only one gift";
default -> number + " gifts";
};
}
}
关于switch表达式的详细介绍,请参考下面的这个视频。

模式匹配中的模式由两个部分组成,分别是模式 predicate 和模式变量。模式 predicate 用来判断模式是否匹配目标对象,而模式变量则负责在匹配成功时,从目标对象中抽取值。
Java 17可以使用的模式包括 instanceof 操作符和 switch 表达式。
下面的代码给出了模式匹配的示例。
public class ObjectFormatter {
public String format(Object input) {
return switch (input) {
case null -> "";
case Number n -> NumberFormat.getNumberInstance().format(n);
case LocalDateTime t -> t.format(DateTimeFormatter.ISO_DATE_TIME);
default -> input.toString();
};
}
} 关于模式匹配的详细信息,请参考下面的这个视频。

文本块允许在Java源代码中添加多行的字符串常量。
下面的代码给出了文本块的使用示例。
public class SimpleTextBlocks {
private static final String BLOCK1 = """
<user>
<id>001</id>
<name>alex</name>
<email>alex@example.com</email>
</user>
""";
private static final String BLOCK2 = """
{
"id": "001",
"name": "alex",
"email": "alex@example.com"
}
""";
} 关于文本块的详细信息,请参考下面的这个视频。

打包工具 jpacakge 可以把Java应用打包成 Windows、macOS 和 Linux上的安装文件。安装之后可以直接运行应用。JDK会被自动打包。jpackage 支持对JDK镜像中的模块进行定制。
关于 jpackage 的详细信息,请参考下面的这个视频。

JFR(Java Flight Recorder) 事件流API允许以流的方式对JFR事件进行异步处理。传统的使用JFR的方式需要启动录制,让应用运行,停止录制之后得到JRF的事件文件,最后再进行分析。这种方式并不适用实时监控的场景,而JRF事件流API可以解决这个问题。
JVM常量API提供了一套标准的API来描述JVM中常量池中的常量。该API在包 java.lang.constant 中。该API主要是提供给字节代码操纵库来使用的。
隐藏类无法被其他类的字节代码来引用。隐藏类只能通过Java反射API来使用。隐藏类的主要作用是让框架在运行时生成无法被发现的类。隐藏类只与类加载器保持松散的联系。当没有对隐藏类的引用时,该类可以被卸载。这一点与普通类是不同的。
Java标准库也有一些改动。
Socket channel 和 server-socket channel 可以使用 Unix 域 socket。
Socket 和 DatagramSocket API的底层实现进行了重写。
伪随机数生成器的API进行了重构和加强。
新增macOS上基于Metal框架的新的Java 2D API渲染引擎。
新的加密算法:Edwards-Curve Digital Signature。
JVM级别的序列化过滤器的工厂对象,可以根据不同的上下文来创建实际使用的序列化过滤器对象。
CDS(Class-Data Sharing)可以提升JVM的启动速度。从Java 10,CDS支持对应用中的类进行处理。不过使用应用CDS(AppCDS)的操作比较繁琐。一共需要3个步骤才能完成。从Java 13开始,启动 AppCDS 只需要两步即可。第一步是运行一次应用,JVM会自动相应的CDS归档文件。第二步是使用自动生成的CDS归档文件来启动应用。
NullPointerException的消息会包含具体的为null的位置信息,方便错误调试。
JVM的元空间的内存分配算法进行了修改,降低了类加载器的开销。
新增了ZGC和Shenandoah两种低暂停时间的GC方式。
CMS回收器已经被删除。
G1回收器做了较大的更新。
外部方法和内存API(Foreign Function & Memory API)目前还是孵化功能。这个API提供了新的更安全并且性能更好的方式来访问外部内存,可以替代对 ByteBuffer 和 Unsafe 的使用。同时也支持与 C 原生代码的交互,可以替代 JNI。
关于Java 11到Java 17的升级就介绍到这里。所有这些新特性的介绍都有相关的源代码,见GitHub(VividcodeIO/from-java11-to-java17)。