俗话说得好:你发任你发,我用 Java 8,但不得不说新版本 JDK 中引入的一些特性让人眼前一亮,在新项目中可以考虑采用新版 JDK 进行开发,并且 Spring Boot 3 的最低支持版本也需 JDK 17 了,又给了你一个新项目抛弃 Java 8 的理由。

本文即介绍不同版本 JDK 中我用的较多的新特性。

一、安装配置

1. JDK下载

在开始直接先进入 Oracle JDK 官网下载相应的 JDK 版本,为了更方便同时安装多个版本,建议下载 zip 格式解压即享,官网直达

新版 JDK 并没有自带 jre 目录,因此我们需要手动编译生成,进入上述解压后的文件执行下述命令。

# Windwos 下执行
binjlink.exe --module-path jmods --add-modules java.desktop --output jre

# Linux 下执行
bin/jlink --module-path jmods --add-modules java.desktop --output jre

2. IDEA配置

若要在 IDEA 中指定工程的 JDK 版本,需要完成下述两个步骤。

项目配置在 IDEA 左上角 File 选择 Project Structure 按照下图修改,添加上述解压的 JDK 目录即可。同时可以选择图中的 modules 为工程的每个模块手动设置 JDK 版本。

Project Structure

编译配置在 IDEA 中左上角 File 选择 Settings 按照下图修改编译版本。

Java Compiler

3. Maven配置

  • 文件配置在 Maven 工程的 POM 文件中添加下述属性配置用于指定 JDK 依赖版本,完成后刷新配置即可。xml<properties> <maven.compiler.source>17</maven.compiler.source> <maven.compiler.target>17</maven.compiler.target> </properties>
  • 编译配置在通过 Maven 进行 install 时若提示 警告:源发行版17需要目标发行版17 等类似信息时是因为 IDEA 中配置 Maven 版本与实际项目的版本不匹配,需要手动在 IDEA 配置中修改。在 IDEA 中左上角 File 选择 Settings 按照下图修改编译版本。
Maven Runner

二、JDK 9

1. 集合

在 JDK 9 中为 List 等集合提供了 of() 初始化方式,实现更便捷的默认值初始化。

相应的使用示例如下:

public void collectionDemo() {
    Set<Integer> set = Set.of(1, 2, 3);
    System.out.println("Set: " + set);

    List<Integer> list = List.of(1, 2, 3);
    System.out.println("List: " + list);

    Map<Integer, String> map = Map.of(1, "1", 2, "2", 3, "3");
    System.out.println("Map: " + map);
}

2. Stream

在 JDK 8 中引入了全新特性 stream,而在 JDK 9 中为 stream 添加了两个全新接口 takeWhile() 与 dropWhile(),具体信息如下:

方法 作用
takeWhile() 当满足提供的条件时停止。
dropWhile() 与 takeWhile 相反,当满足提供的条件时开始。

在使用 takeWhile() 与 dropWhile() 接口前,可通过 stream 的 sorted() 接口对目标集合按照一定规则进行排序,使得目标集合呈现一定的线性排列,从而更好的应用上述两类方法。

public void streamDemo() {
    List<Integer> list = new ArrayList<>();
    for (int i = 1; i < 5; i++) {
        list.add(i);
    }
    List<Integer> list1 = list.stream()
            // Break when "t > 3"
            .takeWhile(t -> t < 3)
            .toList();
    // list1: 1, 2
    System.out.println("List takeWhile: " + list1);

    List<Integer> list2 = list.stream()
            // Start from "t > 3"
            .dropWhile(t -> t < 3)
            .toList();
    // list2: 3, 4
    System.out.println("List dropWhile: " + list2);
}

3. 私有接口

在 JDK 8 引入了默认接口方法与静态接口方法,而在 JDK 9 提供了私有接口方法以达到代码复用的目的。

通过 private 声明私有接口方法,其仅能在当前接口类中使用。

public interface Calculator {

    default void sayHello(String message) {
        saySomething("Hello World!");
    }
    
    default void sayGoodbye(String message) {
        saySomething("Goodbye!");
    }

    /**
     * 私有接口方法
     */
    private void saySomething(String message) {
        System.out.println("Call private interface, message: " + message);
    }
}

4. 其它特性

  • G1 垃圾收集器在年轻代将不会回收 Humongous 块的对象空间。

三、JDK 11

1. var定义

在 JDK 11 中引入了 var 关键字实现泛化定义,在声明声明对象时无需显式指定对象类型,由系统自动识别。

如下中通过 var 定义了 int 变量与 String 变量。

public void varDemo() {
    var a = 10;
    System.out.println("a: " + a);

    var str = "hello world!";
    System.out.println("Str: " + str);
}

2. 字符方法

在 JDK 11 中同时为字符串新增了一系列常用接口,具体描述如下:

方法 作用
isBlank() 判断字符串是否为空或内容皆为空格。
isEmpty() 判断字符串长度是否大于 0。
repeat(n) 用于重复拼接字符串内容。

上述方式的相应示例如下:

public void strDemo() {
    var msg = "";
    System.out.println("isBlank: " + msg.isBlank());
    System.out.println("isEmpty: " + msg.isEmpty());

    var str = "ibudai";
    // ibudaiibudai
    System.out.println("repeat: " + str.repeat(2));
}

3. 其它特性

  • 完善 HTTP 客户端 API 的标准化,提供更加便捷的操作 HTTP 请求,但我们更多使用的为 OkHttp 等封装类库,使用 JDK 自带的场景较少,这里不做详细介绍。
  • 正式引入 ZGC(Z Garbage Collector) 垃圾回收器,其为一种低停顿垃圾回收器,可以更好地利用内存,减少停顿时间。

四、JDK 13

1. switch

在 JDK 13 中为 switch 引入了全新的表达式语法,并在 JDK 14 中进一步得到完善。

下面通过一个具体的示例演示 switch 表达式的作用。

public void switchDemo() {
    int num = 1;
    int value = switch (num) {
        case 1, 2 -> num += 1;
        case 3, 4 -> num += 2;
        default -> num += 3;
    };
    System.out.println(value);
}

上述的表达式对应的传统 switch 定义方式如下:

public void switchDemo() {
    int num = 1;
    int value = 0;
    switch (num) {
        case 1:
        case 2:
            value = num + 1;
        case 3:
        case 4:
            value = num + 2;
        default:
            value = num + 3;
    };
    System.out.println(value);
}

2. yield

在 switch 表达式中每个 case 对应的内容为一个函数, break 无法在此使用用于结束分支。因此 JDK 13 引入全新关键字 yield 用于结束分支并返回结果。

yield 关键字作用类似于 break 于 return 的结合,但 break 仅有中断效果没有返回结果功能,而 return 是结束整个方法块但 yield 仅结束对应的 case 分支。

相应的 yield 关键字使用示例如下:

public void switchDemo() {
    int num = 1;
    int value = switch (num) {
        case 1, 2 -> {
            yield num + 1;
        }
        case 3, 4 -> {
            yield num + 2;
        }
        default -> {
            yield num + 3;
        }
    };
    System.out.println(value2);
}

3. 字符块

在 JDK 13 中为字符串引入了新的定义方式,即可通过三个引号 """ 声明,从而避免传统的字符冲突需要使用转义字符。

如下 str 对象定义了一个 json 对象,通过 """ 即可省略旧版中转义字符的使用,同时也提高了可读性。

public void strDemo() {
    String str = """
            {
              "name": "alex"
            }
            """;
    System.out.println(str);
}

五、JDK 14

1. record

在 JDK 14 中引入新的关键字 record 用于 bean 对象,默认为对象重写了 equals()toString()hashCode() 方法,并提供便捷的 getter 方法。

相应的 record 使用示例如下,通过 对象.属性 的方式即可实现对象成员变量的访问。

public record User(String id, String name) {

}

public class Jdk14Test {
    public void recordDemo() {
        User user = new User("123", "Alex");
        String id = user.id();
        String name = user.name();
        System.out.println("id: " + id);
        System.out.println("name: " + name);
        System.out.println("user: " + user);
    }
}

2. 其它特性

新增了低延迟垃圾器,通过 UnlockExperimentalVMOptions 参数指定是否激活。

# ZGC on Windows
-XX:+UnlockExperimentalVMOptions -XX:+UseZGC

# ZGC on macOS
-XX:+UnlockExperimentalVMOptions -XX:+UseZGC

六、JDK 17

1. sealed

在 JDK 17 中引入了新关键词 sealed 与 permits 用于限定类的继承关系。

sealed 关键字可作用于类(class)或接口(interface),通过 permit 关键字指定允许继承的子类,只有被 permits 的类才允许继承类,同时继承的子类也需标注其是否为 sealed 或 non-sealed

public sealed class Human permits Student {
    private String name;
}

public non-sealed class Student extends Human {

}

// 非法,Teacher 未在 permits 集合中
public class Teacher extends Human {

}

2. switch

在 JDK 17 中为 switch 表达式升级了模式匹配,能够实现更灵活的类型比对判断。

如下为官方文档中提供的示例代码:

public String patternSwitch(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();
    };
}