JDK 8 -> 20 最重用的新增特性
[yun]
Posted on April 9, 2023
序
Java 8 早已经在 2014 年发布,毫无疑问 Java 8 对 Java 来说绝对算得上是一次重大版本更新,它包含了十多项语言、库、工具、JVM 等方面的十多项新特性。比如提供了语言级的匿名函数,也就是被官方称为 Lambda 的表达式语法,比如函数式接口,方法引用,重复注解。再比如 Optional 预防空指针,Stearm 流式作,LocalDateTime 时间操作等等。
如今,在2023年3月21日,Java 20已正式发布,但不是一个长期支持版本(LTS)。目前的长期支持版是Java 17。在这篇章LTS不是重点,我们把中重点放在从Java 8发展到现在的Java 20到底新增什么重要的特性。这里,我只总结了11个我个人认为重要的标准新特性(不是预览的),只要你是用Java 17,这些新特性你都能用得上。
Table Of Contents
JDK 9
集合工厂方法
在 JDK 9 中为集合的创建增加了静态工厂创建方式(Collection Factories)。这个集合工厂可以只用一行代码来创建一些较小的集合,目前支持List
, Set
和Map
。这个方法只适合用于创建只读集合(Immutable Collection),里面的对象不可改变。
在 JDK 8 中:
Set<String> mySet = new HashSet<String>();
mySet.add("yun");
mySet.add("yyx");
mySet.add("andy");
mySet = Collections.unmodifiableSet(mySet);
而在 JDK 9 中只需:
Set<String> mySet = Set.of("yun", "yyx", "andy");
JDK 10
局部类型推断
如何你是 Javascript 程序员,你一定熟悉var
这个syntax。在 JDK 10 也加入var
来实现自动推断数据类型。但这个还是和 Javascript 的var
很不一样的。在 Java 里只是一个新的语法,它在编译时就把var
根据转化成具体的数据类型了。
传统写法:
String str = "https://dev.to/";
URL url = new URL(str);
URLConnection con = url.openConnection();
JDK 10简化版:
var str = "https://dev.to/";
var url = new URL(str);
var con = url.openConnection();
虽然var
简化了 Java 代码,但也必须知道var
的使用场景和避免危险使用。比如以下的写法是有后患的:
var flag = 0; // 这里默认是int,而不是short或byte
var items = new ArrayList<>(); // 这里默认是ArrayList<Object>
OpenJDK官方也给出了LVTI的使用指南,往后我再整理一篇简介的文章。
JDK 14
Switch 表达式
Switch 表达式早在 JDK 12 就推出了,而正式标准化是在 JDK 14。主要是加入了->
和yeild
语法在switch
语法里。
传统写法:
String season;
switch(month){
case MAR:
case APR:
case MAY:
season = "spring";
break;
case JUN:
case JUL:
case AUG:
season = "summer";
break;
case SEP:
case OCT:
case NOV:
season = "autumn";
break;
case DEC:
case JAN:
case FEB:
season = "winter";
break;
default:
throw new IllegalArgumentException("Not a month: " + month);
}
return season;
Switch 表达式简化后:
return switch(month){
case MAR, APR, MAY -> "spring";
case JUN, JUL, AUG -> "summer";
case SEP, OCT, NOV -> "autumn";
case DEC, JAN, FEB -> "winter";
}
更清晰的NPE
在 JDK 14 之前,NullPointerException
(NPE)只汇报哪一行代码导致空指针,在有多个表达式的代码里是很难知道那个对象为null
。在 JDK 14,这个 exception 已加入清晰的 message 告诉你哪个对象是null
。
比方这行代码,如果b
是null
a.i = b.j;
在 JDK 14 之前的报错是这样的:
Exception in thread "main" java.lang.NullPointerException
at App.main(App.java:5)
而 JDK 14 清晰的表明:
Exception in thread "main" java.lang.NullPointerException:
Cannot read field "j" because "b" is null
at App.main(App.java:5)
JDK 15
文本块
在 Java 里长文本字符串连接一直以来都是一个恶梦,因为不仅写起来麻烦,读起来也很恶心。终于在 JDK 15 加入了文本块。直接来看看对比把。
之前的字符串连接:
String json = "{\n" +
"\"name\": \"" + user.name + "\",\n" +
"\"nickname\": \"" + user.nickName + "\"\n" +
"}";
JDK 15 的文本块:
var json = """
{
"name": "%s",
"nickname": "%s"
}
""".formatted(user.name, user.nickName);
ZGC: 可扩展低延迟垃圾收集器
在 JDK 15,ZGC 垃圾收集器正式发布。根据 SPEC (Standard Performance Evaluation Corporation) 的测试,ZGC 拥有着不俗的性能:
而且还在 JDK 版本更新中不断优化:
那么 ZGC 那么好,为什么没有取代 G1GC 作为默认的 GC 呢?我个人认为有几个原因。GC 在 Java 里是一个很深学问,目前没有一个方案可以解决全部的使用场景。而 G1GC 已被广泛使用,表现也比较稳定,如果突然默认改了去用 ZGC 可能会出现不可预算的问题而导致大量不太了解 GC 的 Java 用户陷入困境。也有很多 Java 用户做 GC 调试只在默认 GC 上做调试,如果冒昧的升级用了不同的 GC,不读升级文档的 Java 用户酱不懂发生了什么事导致调试失效。
JDK 16
instanceof 模式匹配
这个 Pattern Matching 的升级略为直接,就是简化了instanceof
使用场景。这里直接举例:
传统写法:
if( obj instanceof String){ // 1) 检查 obj 的类
// 2) declare 新的 String 变量 str,3) 并 cast obj 去 String 类
String str = (String) obj;
// 使用 str
}
上面的 3 步现在可以简化成 1 步了:
if( obj instanceof String str ){
// 直接可以使用 str
}
Record 类
JDK 16 加入了record
类。我相信大部分 Java 用户都用过Lombok
工具库吧,这个record
类和Lombok
的@Data
类似,它会自动编译出hashcode
、equals
、toString
等方法。但record
是一个final class
,同时所有的属性都是final
修饰,所以record
只有getter
而已。更符合 Record 这么名词,就是不可更改(Immutable)的。
JDK 16 之前:
final class Point {
private final int x;
private final int y;
Point(int x, int y) {
this.x = x;
this.y = y;
}
int x() { return x; }
int y() { return y; }
public boolean equals(Object o) {
if (!(o instanceof Point)) return false;
Point other = (Point) o;
return other.x == x && other.y == y;
}
public int hashCode() {
return Objects.hash(x, y);
}
public String toString() {
return String.format("Point[x=%d, y=%d]", x, y);
}
}
Lombok
写法:
@Getter
@ToString
@EqualsAndHashCode
@AllArgsConstructor
final class Point{
final int x;
final int y;
}
JDK 16 record
写法:
record Point(int x, int y) { }
JDK 17
Java 17 是一个长期支持(LTS)版本,有较多的更新,这里我只列出两个我认为比较值得一提的特性。
密封类
sealed
密封类用来限制class
和interface
的继承的机制,可以说是灵活版的final
。被sealed
修饰的类可以指定子类,而且sealed
修饰的类的机制具有传递性,而它的子类必须使用final
、sealed
、non-sealed
来修饰。
打个比方:
public abstract sealed class Shape
permits Circle, Rectangle, Square, WeirdShape { ... }
public final class Circle extends Shape { ... }
public sealed class Rectangle extends Shape
permits TransparentRectangle, FilledRectangle { ... }
public final class TransparentRectangle extends Rectangle { ... }
public final class FilledRectangle extends Rectangle { ... }
public final class Square extends Shape { ... }
public non-sealed class WeirdShape extends Shape { ... }
Switch 模式匹配 (预览)
虽然在 JDK 16 加入了instanceof
模式匹配,但有些使用场景,写起代码来还是比较麻烦的。在 JDK 17 把模式匹配引入到switch
里。虽然这个是预览,但我觉得这个特性挺有用的,就说说这个用在switch
里的模式匹配。
使用instanceof
模式匹配:
static String formatter(Object o) {
String formatted = "unknown";
if (o instanceof Integer i) {
formatted = String.format("int %d", i);
} else if (o instanceof Long l) {
formatted = String.format("long %d", l);
} else if (o instanceof Double d) {
formatted = String.format("double %f", d);
} else if (o instanceof String s) {
formatted = String.format("String %s", s);
}
return formatted;
}
新的switch
写法简洁很多:
static String formatterPatternSwitch(Object o) {
return switch (o) {
case Integer i -> String.format("int %d", i);
case Long l -> String.format("long %d", l);
case Double d -> String.format("double %f", d);
case String s -> String.format("String %s", s);
default -> o.toString();
};
}
JDK 18
默认 UTF-8 字符编码
最后提一提用纯英文写代码的不太关心的改进,而用中文写代码需要知道的,就是默认字符编码。在 JDK 18 把默认字符编码设置成了 UTF-8。这样有用字符编码的 APIs 都能在开发,操作系统和配置上保持一致性。
总结
JDK 17 已改进与新增了挺多很有用的特性,以上是我个人认为帮助到我平日写代码的,不仅能写出更清晰的代码(readability),在性能上也有大幅的提升。
如果你还用着 JDK 8,强力建议尽快升级到 JDK 17,一般上是不会有太多的问题,必须注意的是 GC,也许默认的 GC 是使用 Serial GC,而新版是使用 G1GC。
Posted on April 9, 2023
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.
Related
November 29, 2024