长期支持版的 Java21 在 9 月 19 日发布了,看一下有哪些新特性,主要看看正式加入的特性,预览特性就浏览一遍就行了,毕竟预览特性随时都会变化的。
正式特性 Sequenced Collections | 有序集合 Java 的集合框架缺少一种能够表示具有定义好的排列顺序的元素序列(比如 List 和 Deque 等)的集合类型,以及对这类集合的操作集。
来看看以前获取序列元素的操作:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 public void getCollectionElementTest () { List<Integer> list = List.of(1 , 2 , 3 , 4 , 5 ); list.get(0 ); list.get(list.size() - 1 ); Deque<Integer> deque = new LinkedList <>(list); deque.getFirst(); deque.getLast(); SortedSet<Integer> set = new TreeSet <>(list); set.getFirst(); set.getLast(); LinkedHashSet<Integer> linkList = new LinkedHashSet <>(list); Iterator<Integer> iterator = linkList.iterator(); while (iterator.hasNext()) { Integer integer = iterator.next(); } }
想要拿到序列的第一个元素和最后一个元素,列表使用下标直接获取,队列和有序集合有另外的实现方法,链表就只能遍历获取了,没有一个集合接口来归类这类有顺序的序列,具体的获取元素的操作方法也各不相同。
为了解决这些问题引入了 SequencedCollection
接口,此接口继承自 Collection
接口,具体可以看下图:
List
和 Deque
接口实现了SequencedCollection
接口;SortedMap
和LinkedHashMap
实现了SequencedMap
接口。现在列表和链表都能使用统一的方式获取首尾元素了。
1 2 3 4 5 6 7 8 9 10 @Test public void getCollectionElementNewTest () { List<Integer> list = List.of(1 , 2 , 3 , 4 , 5 ); list.getFirst(); list.getLast(); LinkedHashSet<Integer> linkList = new LinkedHashSet <>(list); linkList.getFirst(); linkList.getLast(); }
Virtual Threads 虚拟线程 虚拟线程详细看还在预览的时候之前写过一篇文章《简单了解下 JDK19 预览版的 Virtual Threads》 ,正式特性也没太大变化,之前写的测试用例都能正常跑通。
Record Patterns | 记录模式 Java16 开始新增了 record 类,新增的 Record Patterns 特性就是对 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 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 public class RecordPatternsExample { @Test public void patternTest () { Point p = new Point (1 , 2 ); printSum(p); } public void printSum (Object obj) { if (obj instanceof Point p) { int x = p.x(); int y = p.y(); System.out.println(x+y); } if (obj instanceof Point (int x, int y) ) { System.out.println(x + y); } } @Test public void nestTest () { ColoredPoint cp = new ColoredPoint (new Point (1 , 2 ), ColInheritingor.BLUE); printPointY(cp); } public void printPointY (Object obj) { if (obj instanceof ColoredPoint coloredPoint) { if (coloredPoint.p() != null ) { System.out.println(coloredPoint.p().y()); } } if (obj instanceof ColoredPoint (Point p, Color c) ) { System.out.println(p.y()); System.out.println(c); } } } record ColoredPoint (Point p, Color c) {}enum Color { RED, GREEN, BLUE}record Point (int x, int y) { public Point (int x) { this (x, 0 ); } }
Pattern Matching for switch | switch 的模式匹配 这也是一个增强的特性,目的是扩展 switch 表达式的适用性。
看例子,先定义一些类型:
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 sealed abstract class Person permits Tom, Jack, Nathan { int age; Person(int age) { this .age = age; } } final class Tom extends Person { public Tom (int age) { super (age); } } final class Jack extends Person { public Jack (int age) { super (age); } } final class Nathan extends Person { public Nathan (int age) { super (age); } }
判断用户是谁可以这么操作:
1 2 3 4 5 6 7 8 9 10 11 @Test public void whenTest () { Person person = new Tom (30 ); switch (person) { case null -> System.out.println("null!" ); case Jack jack -> System.out.println("I am jack" ); case Tom tom when tom.age == 30 -> System.out.println("Tom's age is 30" ); default -> System.out.println("Who am i?" ); } }
在以前我们需要用到 instanceof 来一个个判断 person 是谁,现在可以用 switch 的模式匹配来简化操作,甚至在 case 语句中还可以用 when 语句来对对象的属性做判断。
由于 Person 这里定义的是一个密闭类,即创建的对象只能是指定的几种类型,所以穷尽 case 之后的 default 可以省略。
1 2 3 4 5 6 7 8 9 10 11 12 @Test public void coverageTest () { Person person = new Nathan (29 ); switch (person) { case Tom tom -> System.out.println("I am tom." ); case Jack jack -> System.out.println("I am jack." ); case Nathan nathan -> System.out.println("I am nathan." ); }; }
也可以和记录模式的搭配使用:
1 2 3 4 5 6 7 8 9 10 11 @Test public void recordTest () { record MyPair <S,T>(S fst, T snd){}; MyPair<String, Integer> akari = new MyPair <>("Akari" , 30 ); switch (akari) { case null -> System.out.println("null!" ); case MyPair (var name, var age) -> System.out.printf("username:%s, user age:%d" , name, age); default -> System.out.println("default" ); } }
预览特性 String Templates | 字符串模板 对字符串的操作是日常开发的高配功能,Java12 带来了处理字符串的新方式。
字符串模板处理器类 STR 用于将表达式中的变量进行字符串插值,最后返回字符串。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 @Test public void STRTest () { String name = "nathan" ; String str = STR."I am \{name}." ; System.out.println(str); int a=10 , b=20 ; int [] arr = {1 , 2 , 3 }; System.out.println(STR."\{a} + \{b} = \{a+b}" ); System.out.println(STR."Tody is \{LocalDate.now()}." ); System.out.println(STR."\{a++}, \{a++}, \{arr[0]}" ); }
在 Java15 已经可以定义多行文本块,字符串处理也能配合使用,看下面的多行模板表达式例子。
1 2 3 4 5 6 7 8 9 10 11 12 @Test public void mutilLineTest () { var version = 21 ; var str = STR.""" <html> <body> <p>Java \{version} is now available!</p> </body> </html> """ ; System.out.println(str); }
除了 STR 之外还有一个 FMT 模板处理器,它除了具备 STR 一样的插值功能外还能做左侧格式化处理。
对比以下 STR 和 FMT 的处理方式和输出。
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 public void FMTTest () { record Rectangle (String name, double width, double height) { double area () { return width * height; } } Rectangle[] zone = new Rectangle [] { new Rectangle ("Alfa" , 17.8 , 31.4 ), new Rectangle ("Bravo" , 9.6 , 12.4 ), new Rectangle ("Charlie" , 7.1 , 11.23 ), }; String strTable = STR.""" Description Width Height Area \{zone[0].name} \{zone[0].width} \{zone[0].height} \{zone[0].area()} \{zone[1].name} \{zone[1].width} \{zone[1].height} \{zone[1].area()} \{zone[2].name} \{zone[2].width} \{zone[2].height} \{zone[2].area()} Total \{zone[0].area() + zone[1].area() + zone[2].area()} """ ; String fmtTable = FMT.""" Description Width Height Area %-12s\{zone[0].name} %7.2f\{zone[0].width} %7.2f\{zone[0].height} %7.2f\{zone[0].area()} %-12s\{zone[1].name} %7.2f\{zone[1].width} %7.2f\{zone[1].height} %7.2f\{zone[1].area()} %-12s\{zone[2].name} %7.2f\{zone[2].width} %7.2f\{zone[2].height} %7.2f\{zone[2].area()} \{" ".repeat(28)} Total %7.2f\{zone[0].area() + zone[1].area() + zone[2].area()} """ ; System.out.println(strTable); System.out.println(fmtTable); }
此外还有一个 RAW 标准模板处理器,有待处理元数据可先用 RAW 写好模板,等拿到变量值再做插值。另外,为了保证安全,如果单纯写一个模板字符串会有检查错误。
1 2 3 4 5 6 public void RAWTest () { String name = "Joan" ; StringTemplate st = RAW."My name is \{name}." ; System.out.println(STR.process(st)); }
模板处理器 STR 和 FMT 是功能接口 StringTemplate.Processor 的实例,实现该接口的抽象方法 process,该方法接受 StringTemplate 并返回一个对象。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 @Test public void interfaceTest () { int x = 10 , y = 20 ; StringTemplate st = RAW."\{x} plus \{y} equals \{x + y}" ; System.out.println(st.fragments()); System.out.println(st.values()); var INTER = StringTemplate.Processor.of((StringTemplate _st) -> { StringBuilder sb = new StringBuilder (); Iterator<String> fragIter = _st.fragments().iterator(); for (Object value : _st.values()) { sb.append(fragIter.next()); sb.append(value); sb.append("_" ); } sb.append(fragIter.next()); return sb.toString(); }); System.out.println(INTER."\{x} plus \{y} equals \{x + y}" ); }
Unnamed Patterns and Variables | 未命名模式和变量 这个特性主要是用来简化代码的,如果在代码中声明了变量但又用不上,可以用下划线 _
代替,在记录模式和平常的循环和异常处理代码块中能用到,需要注意的是别跟用下划线开头做变量混淆了。
在记录模式中的使用:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 record Point (int x, int y) { }record User (Order order) {}record Order () {}@Test public void unnamedPatternTest () { Point p = new Point (1 , 2 ); unnamedPattern(p); } public void unnamedPattern (Object obj) { if (obj instanceof Point (int x, _) ) { System.out.printf("x value is:%d." , x); } switch (obj) { case User (_) -> System.out.printf("user object." ); case Point (_, _) -> System.out.println("point object." ); default -> System.out.printf("default output." ); } }
在循环和异常处理代码块中的使用:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 @Test public void unnamedVariablesTest () { Queue<Integer> list = new LinkedList <>(List.of(1 , 2 , 3 )); int sum = 0 ; for (Integer _ : list) { sum += 10 ; } while (list.size() > 1 ) { var _ = list.remove(); } int _ = sum; try { Point _ = new Point (1 , 2 ); } catch (Exception _) { throw new RuntimeException (); } }
Unanmed class and instance Main method 未命名类和实例的 | Main 方法 未命名类和实例的 Main 方法由于还是预览特性,IDEA 也还没有支持,可以用命令行来测试。
新建一个 Main.java 文件,编辑内容:
1 2 3 4 5 void main () { System.out.println(str); } String str = "Unanmed class and instance main method." ;
使用命令编译:
1 2 3 $ ~/app/jdk/jdk-21/bin/javac --release 21 --enable-preview Main.java Note: Main.java uses preview features of Java SE 21. Note: Recompile with -Xlint:preview for details.
输出:
1 2 $ ~/app/jdk/jdk-21/bin/java --enable-preview Main Unanmed class and instance main method.
参考