Java17已经于2021年3月16日发布,按照Oracle的规则,这是一个LTS(长期支持版),本篇学习并记录下该版本的新特性。
密封类(Sealed Classes) JEP409 这是 Java 17 最重要的语法特性。它允许类或接口的作者控制哪些代码可以实现或继承它们。这为 代数数据类型 (ADT) 提供了基础。
例如:
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 37 38 39 40 package cn.net.dev;sealed interface Shape permits Circle, Square {} final class Circle implements Shape { private final double radius; public Circle (double radius) { this .radius = radius; } public double radius () { return radius; } } non-sealed class Square implements Shape { private final double side; public Square (double side) { this .side = side; } public double side () { return side; } } public class SealedDemo { public static void main (String[] args) { Shape shape = new Circle (5.0 ); System.out.println("面积 " + area(shape)); } static double area (Shape shape) { if (shape instanceof Circle c) { return Math.PI * c.radius() * c.radius(); } else if (shape instanceof Square s) { return s.side() * s.side(); } else { throw new IllegalStateException ("Unknown shape" ); } } }
记录类(Records) JEP 359、JEP 384、JEP 395 经过 JEP 359、JEP 384、JEP 395三个版本的迭代,在Java16发布了证书版本,不过我只用LTS的版本,所以就将它向上归类到Java17的新特性
记录类的定义非常灵活,我们可以在单独文件中定义,也可以在类内部定义,甚至在函数内部定义。记录类的使用和普通类无异,使用 new 创建即可。它的特点如下:
它是一个final类;
不能继承其他类(包括记录类)
所有字段都是final的,一旦创建就不可以修改。
内置实现了构造函数,构造参数就是所有字段
内置实现了getter方法。
内置实现了equals()、hashCode()、toString()方法
使用该类型,可以极大的简化不可变数据传输对象的编写。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 package cn.net.dev;record User (Long id, String name, String email) {} public class RecordDemo { public static void main (String[] args) { User u1 = new User (1L , "zhangsan" , "zhangsan@mail.com" ); User u2 = new User (1L , "lisi" , "lisi@mail.com" ); System.out.println(u1.name()); System.out.println(u1); System.out.println(u1.equals(u2)); } }
文本块 (Text Blocks) JEP 378 文本块是在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 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 package cn.net.dev;public class TextBlockDemo { public static void main (String[] args) { String htmlBefore = "<html>\n" + " <head>\n" + " <title>Hello Java</title>\n" + " </head>\n" + " <body>\n" + " <div>111</div>\n" + " </body>\n" + " </html>" ; String html = """ <html> <head> <title>Hello Java</title> </head> <body> <div>111</div> </body> </html> """ ; String json = """ { "name":"zhangsan", "age":20, "email":"zhangsan@mail.com" } """ ; String sql = """ SELECT id, name FROM users WHERE status = 'ACTIVE' ORDER BY created_at DESC; """ ; System.out.println(json); System.out.println(sql); } }
如果需要转义,变量替换。可以使用如下方法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 String name = "lisi" ;String msg = """ Hello My Name is $name """ .replace("$name" , name);System.out.println(msg); String msg1 = String.format(""" Hello My Name is %s """ , name);System.out.println(msg1); String msg2 = """ Hello My Name is %s """ .formatted(name);
这三种方法都可以,这样就可以避免字符串拼接(”””字符串拼接的用法与普通字符串拼接一致)。
在"""里面,可以使用\来作为<line-terminator>(类似于LINUX中的用法),使用\s转译为单个空格。
1 2 3 4 5 6 7 String text = """ Hello, \ My Name is lisi,\ I love Java.\ """ ; System.out.println(text);
和字符串相关,就避免不了转义,三个连续的 " 字符至少需要有一个 " 进行转义,以避免模仿结束定界符。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 String text1 = """ String text1 = \""" Hello Java \""" """ ;System.out.println(text1); System.out.println(""" 1 " 2 "" 3 ""\" 4 ""\"" 5 ""\""" 6 ""\"""\" 7 ""\"""\"" 8 ""\"""\""" 9 ""\"""\"""\" 10 ""\"""\"""\"" 11 ""\"""\"""\""" 12 ""\"""\"""\"""\" """ );
instanceof 模式匹配 (Pattern Matching for instanceof) 消除了显式的强制类型转换,代码更安全、更简洁。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 package cn.net.dev;public class InstanceofDemo { public static void main (String[] args) { Object obj = "Hello Java 17" ; if (obj instanceof String str) { String string = (String) obj; if (string.length() > 5 ) { System.out.println("这是一个字符串,并且长度大于5" ); } } if (obj instanceof String str && str.length() > 5 ) { System.out.println("这是一个字符串,并且长度大于5" ); } } }
伪随机数生成器增强 JEP411 引入了 RandomGenerator 接口,提供了更丰富的随机数算法(如 LXM 族算法)。
1 2 3 4 5 6 7 8 9 10 11 package cn.net.dev;import java.util.random.RandomGenerator;import java.util.random.RandomGeneratorFactory;public class RandomDemo { public static void main (String[] args) { RandomGenerator g = RandomGeneratorFactory.of("L64X128MixRandom" ).create(); System.out.println("随机数: " + g.nextInt(8 )); } }
Switch表达式 这是Java14正式发布的功能,也是在简化代码开发,我觉得还是有必要使用一下。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 package cn.net.dev;public class SwitchExpDemo { public static void main (String[] args) { String day = "MONDAY" ; int numLetters = switch (day) { case "MONDAY" , "FRIDAY" , "SUNDAY" -> 6 ; case "TUESDAY" -> 7 ; case "THURSDAY" , "SATURDAY" -> 8 ; case "WEDNESDAY" -> 9 ; default -> throw new IllegalArgumentException ("Invalid day " + day); }; System.out.println("字母数 :" + numLetters); } }
Teeing Collector(API增强) Stream API 的增强,允许在一个流中同时执行两个不同的收集操作,并合并结果。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 package cn.net.dev;import java.util.stream.Stream;import static java.util.stream.Collectors.*;public class TeeingDemo { public static void main (String[] args) { var result = Stream.of(1 , 2 , 3 , 4 , 5 , 6 , 7 , 8 , 9 , 10 ).collect( teeing( summingInt(Integer::intValue), counting(), (sum, count) -> sum / (double ) count ) ); System.out.println(result); } }
NullPointerExecption提示更友好 这个是在Java 14正式发布的,虽然也算不上新特性。
1 2 3 4 5 6 A a = new A ();a = null ; a.call(); Exception in thread "main" java.lang.NullPointerException: Cannot invoke "cn.net.dev.A.call()" because "a" is null at cn.net.dev.NpeDemo.main(NpeDemo.java:14 )
我把JDK版本换到Java11
1 2 Exception in thread "main" java.lang.NullPointerException at cn.net.dev.NpeDemo.main(NpeDemo.java:11)
ZGC 大名鼎鼎的ZGC也是在Java 15终于正式发布了。它是一个可伸缩、低延迟的垃圾回收器,目标是无论堆多大,停顿时间都不超过 10ms。在Java启动参数中,添加-XX:+UseZGC即可。
1 java -XX:+UseZGC -Xms8g -Xmx8g -jar app.jar
ZGC的设计哲学是自适应,所以不需要太多的JVM参数,比较常用的如下所示:
参数
说明
建议
-XX:ParallelGCThreads=n
并行GC阶段使用的线程数
默认根据CPU的核心数计算,通常无需手动指定。
-XX:ConcGCThreads=n
并发GC阶段使用的线程数
如果发现GC速度赶不上分配速度,可以适当增加
-Xlog:gc *
开启详细的GC日志
诊断性能瓶颈的必备工具。
-XX:SoftMaxHeapSize=n
软最大堆限制
告诉JVM尽量保持堆在多少以下,超出后会更积极的回收。
一般只需要开启日志即可。
1 java -XX:+UseZGC -Xlog:gc*:file=gc.log:time ,level,tags -jar app.jar
在 gc.log 中,你会看到 ZGC 的三个核心阶段:
Pause Mark Start :停顿(极短)。
Concurrent Mark :并发标记(不影响业务)。
Pause Mark End :停顿(极短)。
Concurrent Relocate :并发迁移(不影响业务)。
2024年1月1日更新:Java21的分代ZGC更靠谱,更加好用。推荐升级到Java 21。
新类层次结构分析实现 (New Class Hierarchy Analysis Implementation) 它属于JDK-8266074,这个不是JEP(JDK Enhancement Proposal),但也了解一下。
CHA 是什么?
CHA (Class Hierarchy Analysis,类层次结构分析) 是 JIT 编译器(C1/C2)用来优化代码的一种编译器技术。
核心目的 :判断一个虚方法(Virtual Method)调用是否只有一个实际的实现类(即“单态性”)。
作用 :如果编译器发现某个方法没有被子类重写,它就会大胆地进行 内联(Inlining) ,直接把方法体代码考过来,避免昂贵的虚方法表查询。
在 Java 17 之前,CHA 的实现逻辑比较古老且受限。JDK-8266074 引入了一套全新的、基于 Vtable(虚函数表) 的分析机制。
更精准的判断 :新实现能更敏锐地处理抽象方法 和默认方法(Default Methods) 。
更好的内联决策 :它能发现更多可以被内联的场景,尤其是在复杂的接口继承体系中。
默认开启 :在 Java 17 中,这个新特性是默认开启的。
虚应用的自动清理 同上,不是JEP,属于JDK-8071507。在 Java 17 中,PhantomReference(虚引用)在对象不可达时会自动被 GC 清理,就像弱引用和软引用一样。减少了手动清理虚引用(以前看书疯狂JAVA讲义,书里翻译的是幻像引用,同一个东西,为了和强、弱一致,用虚更好一点)队列的负担,降低了内存泄漏风险。
还有一些swing和java.net的新特性,就不需要了解了,用到再说。还有一个Ideal Graph Visualizer的现代化 也可以了解下,具体看这个介绍即可。