JDK21新特性

笔者在过去主要使用的JDK版本是JDK8,最近在学习最新的JDK长期支持版本——JDK21,本文将简要记录一下JDK21相比于JDK8的一些新特性,对于一些比较有意思的新特性也会在后续文章中逐步展开研究。

var 关键字局部变量推断 #

// 仅限局部变量
var name = "zhangsan";
var age = 18;
var isMan = true;
var list = new ArrayList<String>();
var map = new HashMap<String, String>();

switch 表达式增强 #


// 假设有枚举定义为
public enum Direction {

    EAST,
    SOUTH,
    WEST,
    NORTH,
    NORTHWEST,
    ;
}

// switch 表达式为变量赋值
var witchCity = switch (direction) {
    case EAST, NORTHWEST -> "成都";
    case WEST -> "上海";
    case SOUTH -> "广州";
    case NORTH -> "北京";
    default -> throw new IllegalArgumentException("Invalid direction: " + direction);
};
// yield 关键字返回值,并跳出switch表达式(相当于break)
var witchCity = switch (direction) {
    case EAST, NORTHWEST:
        System.out.println("成都");
        yield "成都";
    case WEST:
        System.out.println("上海");
        yield "上海";
    case SOUTH:
        System.out.println("广州");
        yield "广州";
    case NORTH:
        System.out.println("北京");
        yield "北京";
    default:
        system.out.println("未知");
        yield "未知";
};
// yield 关键字结合lambda表达式
var witchCity = switch (direction) {
    case EAST, NORTHWEST -> {
        System.out.println("成都");
        yield "成都";
    }
    case WEST -> {
        System.out.println("上海");
        yield "上海";
    }
    case SOUTH -> {
        System.out.println("广州");
        yield "广州";
    }
    case NORTH -> {
        System.out.println("北京");
        yield "北京";
    }
    default -> {
        System.out.println("未知");
        yield "未知";
    }
};

记录类(record class) #

记录类的使用场景是存储数据,尤其是那些纯粹做数据传输的对象如DTO等,其每个字段都是不可变的。

// 定义一个记录类,标注了record关键字,同时指定了name和age两个字段,编译器已经自动为其生成了构造函数、字段访问方法name()和age()。
// 除字段不能被修改、不能继承其他类、不能添加实例字段之外其他的特性与普通类相同。
public record Student(String name, int age) {
    // 不能添加实例字段
    // private String teacher;

    // 构造器可以自定义
    public Student {
        if (age < 0) {
            throw new IllegalArgumentException("年龄不能小于0");
        }
        if (name == null || name.isEmpty()) {
            throw new IllegalArgumentException("姓名不能为空");
        }
    }
    // 可以定义方法

    // 可以实现接口
}

密封类(Sealed Classes) #

密封类的使用场景是定义一个类的继承规则,只有指定的类可以继承该类,其他类不能继承该类。类之间耦合更紧了,但是也提升了类型安全。在编写三方库提供给别人用又不希望被使用者随意继承使用时这个特性会特别有用。

// sealed关键字定义一个密封类,同时permits关键字指定了可以继承该类的类
public sealed class Job permits Author, Teacher {

    public String name() {
        return "renxin";
    }
}

// 定义一个final类为密封类的子类,该类不能被继承
public final class Author extends Job {
    public String name() {
        return "renxin";
    }
}

// 定义一个non-sealed修饰的类为密封类的子类,该类放弃密封性可以被普通类继承
public non-sealed class Author extends Job {
}

子类必须与密封类处于同一个模块(或同一个包,如果没有模块系统)

子类必须显式继承密封类,否则编译不通过

密封类的子类必须被显式声明为final、sealed或non-sealed。这是密封类设计的核心原则。这样做有两方面的好处。

  • 明确类的继承结构,防止继承链失控
  • 与模式匹配结合可以在编译期做到穷举检查
  // Author类、Teacher被明确定义为final类,编译期可以知道该类被哪些子类继承,做到穷举检查
  public abstract sealed class Job permits Author, Teacher {}
  public final class Author extends Job {}
  public final class Teacher extends Job {}
  
  void handle(Job job) {
      switch (job) {
          case Author a -> System.out.println("Author");
          case Teacher t -> System.out.println("Teacher");
      }
// 编译通过,若缺失case Teacher分支,编译错误

虚拟线程(Virtual Threads) #

虚拟线程是一种用户态的轻量级线程,类似于其他语言的协程,它的显著提高了Java程序的并发支持能力。细节请参考笔者另一篇文章:理解虚拟线程

新的HTTP客户端API #

支持同步调用和异步调用,异步调用使用CompletableFuture来返回结果。

    // 同步调用
HttpClient client = HttpClient.newHttpClient();
HttpRequest request = HttpRequest.newBuilder()
        .uri(URI.create("https://httpbin.org/get"))
        .GET()
        .build();
HttpResponse<String> response = client.send(request, HttpResponse.BodyHandlers.ofString());

// 异步调用
CompletableFuture<String> stringBodyCompletableFuture = client.sendAsync(request, HttpResponse.BodyHandlers.ofString())
        .thenApply(HttpResponse::body);

instance of 模式匹配 #

instance of 语法增强,可以省去类型转换步骤。


// jdk8写法
String obj = "read.renxinblog.cn";
    if(obj instanceof String){
String s = (String) obj;
        System.out.

println(s.length());
        }

// jdk21写法
var obj = "read.renxinblog.cn";
    if(obj instanceof
String s){
        System.out.

println(s.length());
        }

新一代GC:ZGC #

相较于上一代的G1,ZGC的性能更加优秀,同时也更加稳定。

  • 几乎所有GC阶段都并发进行,STW时间极短,这意味着GC暂停对应用程序造成的性能波动非常小。
  • 能够支持更大的堆,最多达16TB,并且随着堆的增大,GC性能几乎没有降低,这对于数据密集型的大堆系统非常有用。
  • ZGC能够保证GC停顿时间可控,更容易实现系统的SLO要求。
  • 更高的并发能力同时也意味着更高的CPU消耗。

总之,ZGC以复杂的技术实现换来了极低的延迟时间和大堆性能表现,是G1控制延迟的进化版。性能的提升也带来更高的CPU消耗,但这不是一个缺点,而是一个非常的选择。

其他 #

其他特性还包括Stream API的增强、集合工厂方法、文本块(字符串增强)、日期API增强等,这里不多做介绍。