俗话说得好:你发任你发,我用 Java 8
,但不得不说新版本 JDK
中引入的一些特性让人眼前一亮,在新项目中可以考虑采用新版 JDK
进行开发,并且 Spring Boot 3
的最低支持版本也需 JDK 17
了,又给了你一个新项目抛弃 Java 8
的理由。
本文即介绍不同版本 JDK 中我用的较多的新特性。
一、安装配置
1. JDK下载
在开始直接先进入 Oracle JDK
官网下载相应的 JDK
版本,为了更方便同时安装多个版本,建议下载 zip
格式解压即享,官网直达。
新版 JDK
并没有自带 jre
目录,因此我们需要手动编译生成,进入上述解压后的文件执行下述命令。
# Windwos 下执行
bin\jlink.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
版本,需要完成下述两个步骤。
(1) 项目配置
在 IDEA
左上角 File
选择 Project Structure
按照下图修改,添加上述解压的 JDK
目录即可。
同时可以选择图中的 modules
为工程的每个模块手动设置 JDK
版本。
(2) 编译配置
在 IDEA
中左上角 File
选择 Settings
按照下图修改编译版本。
3. Maven配置
(1) 文件配置
在 Maven
工程的 POM
文件中添加下述属性配置用于指定 JDK
依赖版本,完成后刷新配置即可。
<properties>
<maven.compiler.source>17</maven.compiler.source>
<maven.compiler.target>17</maven.compiler.target>
</properties>
(2) 编译配置
在通过 Maven
进行 install
时若提示 警告:源发行版17需要目标发行版17
等类似信息时是因为 IDEA
中配置 Maven
版本与实际项目的版本不匹配,需要手动在 IDEA
配置中修改。
在 IDEA
中左上角 File
选择 Settings
按照下图修改编译版本。
二、JDK 9
1. 集合
在 JDK 9
中为 List
等集合提供了 of()
初始化方式,实现更便捷的默认值初始化。
常见的 List
与 Set
等集合声明方式如下,但需要注意的是虽然代码更为简洁,但通过 of()
定义的为不可变对象,若对其执行添加或删除等等操作将抛出 UnsupportedOperationException
异常。
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);
}
}
三、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
自带的场景较少,这里不做详细介绍。 CMS
回收器被标记弃用,同时正式引入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
定义的类其字段是隐式 final
的,因此不能修改,同时 record
是一种特殊的类,不能继承其他类,但可以实现接口。
相应的 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();
};
}
七、JDK 19
1. 虚拟线程
在 JDK 19
中引入了新的虚拟线程概念,并在 JDK 21
得到完善加强。
在之前的线程中一个线程则对应一个系统线程,针对轻度任务显然这种方式有点浪费,而虚拟线程可以粗暴的理解将一个系统工作线程进行二次拆分,从而达到更高的资源利用。
虚拟线程的创建方式提供了基础的 Thread.ofVirtual()
与 Thread.startVirtualThread()
方式用于创建单线程,同时通过 Executors.newVirtualThreadPerTaskExecutor()
即可创建虚拟线程池。
public void vtDemo1() throws InterruptedException {
// Start a virtual thread (Base on virtual)
Thread t1 = Thread.ofVirtual()
// Set thread name
.name("vt-1")
// start(): Auto call "thread.start()"
// unstarted(): Needed to manual call for "thread.start()"
.unstarted(() -> {
// Task content
try {
System.out.println("Thread.ofPlatform(): Hello World!");
} catch (Exception e) {
throw new RuntimeException(e);
}
});
t1.start();
t1.join();
Thread t2 = Thread.startVirtualThread(() -> {
// Task content
try {
System.out.println("Thread.startVirtualThread(): Hello World!");
} catch (Exception e) {
throw new RuntimeException(e);
}
});
t2.start();
t2.join();
}
参考链接: