JDK 8 -> 20 最重用的新增特性
Published: 2023-04-09 07:06:11

## 序
Java 8 早已经在 2014 年发布，毫无疑问 Java 8 对 Java 来说绝对算得上是一次重大版本更新，它包含了十多项语言、库、工具、JVM 等方面的十多项新特性。比如提供了语言级的匿名函数，也就是被官方称为 [Lambda 的表达式语法](https://dev.to/andylow/java-8-lambda-5214)，比如函数式接口，方法引用，重复注解。再比如 Optional 预防空指针，Stearm 流式作，LocalDateTime 时间操作等等。

如今，在2023年3月21日，Java 20已正式发布，但不是一个长期支持版本（LTS)。目前的长期支持版是Java 17。在这篇章LTS不是重点，我们把中重点放在从Java 8发展到现在的Java 20到底新增什么重要的特性。这里，我只总结了**11个**我个人认为重要的标准新特性（不是预览的），只要你是用Java 17，这些新特性你都能用得上。

### Table Of Contents <a name="top"></a>
   * [JDK 9](#jdk-9)
      * [集合工厂方法](#9-1)
   * [JDK 10](#jdk-10)
      * [局部类型推断](#10-1)
   * [JDK 14](#jdk-14)
      * [Switch 表达式](#14-1)
      * [更清晰的NPE](#14-2)
   * [JDK 15](#jdk-15)
      * [文本块](#15-1)
      * [ZGC: 可扩展低延迟垃圾收集器](#15-2)
   * [JDK 16](#jdk-16)
      * [instanceof 模式匹配](#16-1)
      * [Record 类](#16-2)
   * [JDK 17](#jdk-17)
      * [密封类](#17-1)
      * [Switch 模式匹配 (预览)](#17-2)
   * [JDK 18](#jdk-18)
      * [默认 UTF-8 字符编码](#18-1)
   * [总结](#end)

---

### JDK 9 <a name="jdk-9"></a>
#### 集合工厂方法 <a name="9-1"></a>
> [JEPS-269 : Convenience Factory Methods for Collections](https://openjdk.org/jeps/269)

在 JDK 9 中为集合的创建增加了静态工厂创建方式（Collection Factories）。这个集合工厂可以只用一行代码来创建一些较小的集合，目前支持`List`, `Set`和`Map`。这个方法只适合用于创建只读集合（Immutable Collection），里面的对象不可改变。

在 JDK 8 中:
```java
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");
```

[回顶部](#top)

---

### JDK 10 <a name="jdk-10"></a>
#### 局部类型推断 <a name="10-1"></a>
> [JEPS-286 : Local-Variable Type Inference](https://openjdk.org/jeps/286)

如何你是 Javascript 程序员，你一定熟悉`var`这个syntax。在 JDK 10 也加入`var`来实现自动推断数据类型。但这个还是和 Javascript 的`var`很不一样的。在 Java 里只是一个新的语法，它在编译时就把`var`根据转化成具体的数据类型了。

传统写法：
```java
String str = "https://dev.to/";
URL url = new URL(str);
URLConnection con = url.openConnection();
```

JDK 10简化版：
```java
var str = "https://dev.to/";
var url = new URL(str);
var con = url.openConnection();
```

虽然`var`简化了 Java 代码，但也必须知道`var`的使用场景和避免危险使用。比如以下的写法是有后患的：
```java
var flag = 0; // 这里默认是int，而不是short或byte
var items = new ArrayList<>(); // 这里默认是ArrayList<Object>
```

OpenJDK官方也给出了[LVTI的使用指南](https://openjdk.org/projects/amber/guides/lvti-style-guide)，往后我再整理一篇简介的文章。

[回顶部](#top)

---

### JDK 14 <a name="jdk-14"></a>
#### Switch 表达式 <a name="14-1"></a>
> [JEPS-361: Switch Expression](https://openjdk.org/jeps/361)

Switch 表达式早在 JDK 12 就推出了，而正式标准化是在 JDK 14。主要是加入了`->`和`yeild`语法在`switch`语法里。

传统写法：
```java
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 表达式简化后：
```java
return switch(month){
    case MAR, APR, MAY -> "spring";
    case JUN, JUL, AUG -> "summer";
    case SEP, OCT, NOV -> "autumn";
    case DEC, JAN, FEB -> "winter";
}
```
[回顶部](#top)

#### 更清晰的NPE <a name="14-2"></a>
> [JEPS-358: Helpful NPE](https://openjdk.org/jeps/358)

在 JDK 14 之前，`NullPointerException`(NPE)只汇报哪一行代码导致空指针，在有多个表达式的代码里是很难知道那个对象为`null`。在 JDK 14，这个 exception 已加入清晰的 message 告诉你哪个对象是`null`。

比方这行代码，如果`b`是`null`
```java
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)
```
[回顶部](#top)

---

### JDK 15 <a name="jdk-15"></a>
#### 文本块 <a name="15-1"></a>
> [JEPS-378: Text Blocks](https://openjdk.org/jeps/378)

在 Java 里长文本字符串连接一直以来都是一个恶梦，因为不仅写起来麻烦，读起来也很恶心。终于在 JDK 15 加入了文本块。直接来看看对比把。

之前的字符串连接：
```java
String json = "{\n" +
    "\"name\": \"" + user.name + "\",\n" +
    "\"nickname\": \"" + user.nickName + "\"\n" + 
    "}";
```

JDK 15 的文本块：
```java
var json = """
        {
            "name": "%s",
            "nickname": "%s"
        }
        """.formatted(user.name, user.nickName);
```

[回顶部](#top)

#### ZGC: 可扩展低延迟垃圾收集器 <a name="15-2"></a>
> [JEPS-377: A Scalable Low-Latency Garbage Collector](https://openjdk.org/jeps/377)

在 JDK 15，ZGC 垃圾收集器正式发布。根据 SPEC (Standard Performance Evaluation Corporation) 的测试，ZGC 拥有着不俗的性能：

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/y3idn5pqet1ypfroyjg1.png)

而且还在 JDK 版本更新中不断优化：

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/0j6quf2n978fh98aks35.jpg)

那么 ZGC 那么好，为什么没有取代 G1GC 作为默认的 GC 呢？我个人认为有几个原因。GC 在 Java 里是一个很深学问，目前没有一个方案可以解决全部的使用场景。而 G1GC 已被广泛使用，表现也比较稳定，如果突然默认改了去用 ZGC 可能会出现不可预算的问题而导致大量不太了解 GC 的 Java 用户陷入困境。也有很多 Java 用户做 GC 调试只在默认 GC 上做调试，如果冒昧的升级用了不同的 GC，不读升级文档的 Java 用户酱不懂发生了什么事导致调试失效。

[回顶部](#top)

---

### JDK 16 <a name="jdk-16"></a>
#### instanceof 模式匹配 <a name="16-1"></a>
> [JEPS-394: Pattern Matching for instanceof](https://openjdk.org/jeps/394)

这个 Pattern Matching 的升级略为直接，就是简化了`instanceof`使用场景。这里直接举例：

传统写法：
```java
if( obj instanceof String){ // 1) 检查 obj 的类
    // 2) declare 新的 String 变量 str，3) 并 cast obj 去 String 类
    String str = (String) obj; 

    // 使用 str 
}
```
上面的 3 步现在可以简化成 1 步了：
```java
if( obj instanceof String str ){
    // 直接可以使用 str 
}
```

[回顶部](#top)

#### Record 类 <a name="16-2"></a>
> [JEPS-395: Records](https://openjdk.org/jeps/395)

JDK 16 加入了`record`类。我相信大部分 Java 用户都用过`Lombok`工具库吧，这个`record`类和`Lombok`的`@Data`类似，它会自动编译出`hashcode`、`equals`、`toString` 等方法。但`record`是一个`final class`，同时所有的属性都是`final`修饰，所以`record`只有`getter`而已。更符合 Record 这么名词，就是不可更改（Immutable）的。

JDK 16 之前：
```java
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`写法：
```java
@Getter
@ToString
@EqualsAndHashCode
@AllArgsConstructor
final class Point{
    final int x;
    final int y;
}
```

JDK 16 `record`写法：
```java
record Point(int x, int y) { }
```

[回顶部](#top)

---

### JDK 17 <a name="jdk-17"></a>

Java 17 是一个长期支持（LTS）版本，有较多的更新，这里我只列出两个我认为比较值得一提的特性。

#### 密封类 <a name="17-1"></a>
> [JEPS-409: Sealed Classes](https://openjdk.org/jeps/409)

`sealed` 密封类用来限制`class`和`interface`的继承的机制，可以说是灵活版的`final`。被`sealed`修饰的类可以指定子类，而且`sealed`修饰的类的机制具有传递性，而它的子类必须使用`final`、`sealed`、`non-sealed`来修饰。

打个比方：
```java
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 { ... }
```

[回顶部](#top)

#### Switch 模式匹配 (预览) <a name="17-2"></a>
> [JEPS-406: Pattern Matching for switch (Preview)](https://openjdk.org/jeps/406)

虽然在 JDK 16 加入了`instanceof`模式匹配，但有些使用场景，写起代码来还是比较麻烦的。在 JDK 17 把模式匹配引入到`switch`里。虽然这个是预览，但我觉得这个特性挺有用的，就说说这个用在`switch`里的模式匹配。

使用`instanceof`模式匹配：
```java
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`写法简洁很多：
```java 
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();
    };
}
```

[回顶部](#top)

---

### JDK 18 <a name="jdk-18"></a>
#### 默认 UTF-8 字符编码 <a name="18-1"></a>
> [JEPS-400: UTF-8 by Default](https://openjdk.org/jeps/400)

最后提一提用纯英文写代码的不太关心的改进，而用中文写代码需要知道的，就是默认字符编码。在 JDK 18 把默认字符编码设置成了 UTF-8。这样有用字符编码的 APIs 都能在开发，操作系统和配置上保持一致性。 

[回顶部](#top)

## 总结 <a name="end"></a>

JDK 17 已改进与新增了挺多很有用的特性，以上是我个人认为帮助到我平日写代码的，不仅能写出更清晰的代码（readability），在性能上也有大幅的提升。
如果你还用着 JDK 8，强力建议尽快升级到 JDK 17，一般上是不会有太多的问题，必须注意的是 GC，也许默认的 GC 是使用 Serial GC，而新版是使用 G1GC。

[回顶部](#top)

官方视频介绍：
https://www.youtube.com/watch?v=NA-sB3zvluE