序
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。
Top comments (0)