当前位置: 首页 > news >正文

JDK8之后的新特性

一、JDK9

版本语法说明
9try-with-resource增强

资源对象可以定义在try之外,必须是final或effectively final

9@SafeVarargs增强能加在private方法上,之前只能加在final或static方法上
9接口支持私有方法
9_不能再作为变量名
9Diamond符号可以用于匿名内部类

1. try-with-resources增强

语法变化

// Java 9 之前:资源必须在 try 括号内声明
try (InputStream is = new FileInputStream("file.txt")) {// 使用资源
}// Java 9 之后:资源可以是外部定义的 final 或 effectively final 变量
InputStream is = new FileInputStream("file.txt");
try (is) { // 直接引用外部变量// 使用资源
}

说明

  • 条件:外部变量必须是final或隐式不可变(effectively final)
  • 目的:减少冗余代码,避免在try括号内重复声明已存在的资源。

示例代码:允许把资源的定义放在try块之外

package com.scnu.v9;import java.io.Closeable;
import java.io.IOException;public class TryWithResource {/*** 1. 数据库资源* 2. 输入、输出流资源* 3. 开源框架中的资源* - Hibernate Session* - MyBatis SqlSession* ...*/static class MyResource implements Closeable {// 实现Closeable接口, 实现close()方法@Overridepublic void close() {System.out.println("释放资源");}}public static void main(String[] args) {// 1. 之前的做法/*MyResource myResource = new MyResource();try {System.out.println(myResource);} finally {// 保证资源能够被释放myResource.close();}*/// 2. tryWithResourceMyResource myResource = new MyResource();// 编译器会在try块之后帮我们加一个finally块调用close方法释放资源try(myResource) {System.out.println(myResource);// myResource = new MyResource();  // 不可重新赋值int i = 1/0;}}
}

2. @SafeVarargs增强

语法变化

// Java 9 之前:仅允许在 static 或 final 方法上使用
@SafeVarargs
public static <T> void safeMethod(T... args) {}// Java 9 之后:允许在 private 方法上使用
@SafeVarargs
private <T> void privateSafeMethod(T... args) {}

说明

  • 背景:@SafeVarargs用于抑制泛型可变参数的堆污染警告;
  • 扩展范围:Java 9允许在private方法上使用,因为这些方法不会被外部调用,不存在堆污染风险。
  • 堆污染是指泛型变量的实际类型与声明的类型不一致,导致在运行时可能引发ClassCastException的问题。它通常发生在泛型可变参数(varargs)或未经检查的类型转换时。

示例代码

package com.scnu.v9;import java.util.List;/*
可变参数与泛型 - 可能出现堆污染- 泛型限制类型的功能失效*/
public class VarVariables {/*Java 9之前,仅允许在static或final方法上使用(方法不能被重写)*/@SafeVarargsstatic List<String>[] test(List<String>... lists) {
//        Object[] objects = lists;
//        objects[0] = List.of(1, 2, 3);  // 不恰当的修改 -> 类型转换异常ClassCastExceptionreturn lists;}@SafeVarargsfinal List<String>[] test2(List<String>... lists) {return lists;}/*JDK9之后,允许在private方法上使用private方法不能被外部调用,也不能被重写,不存在堆污染风险*/@SafeVarargsprivate List<String>[] test3(List<String>... lists) {return lists;}public static void main(String[] args) {List<String>[] lists = test(List.of("1", "2", "3"), List.of("a", "b", "c"));for (List<String> list : lists) {for (String key : list) {System.out.println(key);}}}
}

3. 接口支持私有方法

语法示例

public interface MyInterface {default void publicMethod() {privateMethod(); // 调用私有方法}private void privateMethod() {System.out.println("私有方法");}
}

说明

  • 用途:封装接口内部的公共代码逻辑(通常为default方法共享的代码)
  • 限制:只能是实例方法(非static),且仅在接口内部调用。

示例代码:

package com.scnu.v9;public class PrivateMethodInInterface {interface MyInterface {default void bar() {// 在接口内部调用foo();}/*不想被实现类调用*/private void foo() {System.out.println("私有方法");}}static class MyClass implements MyInterface {@Overridepublic void bar() {// foo();  // 不能调用接口的私有方法MyInterface.super.bar();}}
}

4. 下划线 _ 不能作为变量名

语法变化

// Java 8 允许(但不推荐)
int _ = 10; // Java 9 编译报错
int _ = 10; // 错误:'_' 是保留关键字

说明

  • 原因:为未来可能出现的特殊用途保留 _ (如Java 22的Unnamed Variables);
  • 替代方式:使用其它合法的变量名(如ignored)。

5. Diamond符号(<>)用于匿名内部类

语法示例

// Java 8 之前:匿名内部类必须显式指定泛型类型
List<String> list = new ArrayList<String>() {};// Java 9 之后:支持 Diamond 符号推断
List<String> list = new ArrayList<>() {}; // 允许省略泛型类型

说明

  • 背景:Diamond符号(<>)原本仅用于普通类实例化,Java 9扩展支持匿名内部类;
  • 限制:需确保编译器能通过上下文推断泛型类型。

示例代码:

package com.scnu.v9;import java.util.ArrayList;
import java.util.List;/*
<>符号可以用于匿名内部类*/
public class DiamondWithAnonymousInnerClass {public static void main(String[] args) {List<String> list = new ArrayList<>();  // 简化写法// JDK9之前,必须写完整的泛型声明MyClass<String> myClass = new MyClass<String>() {@Overridepublic void foo(String s) {}};// JDK9及之后,支持匿名内部类简化泛型声明MyClass<String> myClass2 = new MyClass<>() {  // 简化写法@Overridepublic void foo(String s) {System.out.println("---------");}};}interface MyClass<T> {void foo(T t);}
}

6. 可变类型参数的坑

package com.scnu.v9;import java.util.Arrays;public class VarVariablesPrimitiveArray {public static void main(String[] args) {// 1. 传递了3个Object对象test(new Object(), new Object(), new Object());  // new Object[]{new Object(), new Object(), new Object()}System.out.println("--------------------------------");// 2. 传递了3个基本类型的整数test(1, 2, 3);  // new Integer[]{1, 2, 3}System.out.println("--------------------------------");// 3. 传了一个基本类型的整数数组,包含3个整数// Confusing primitive array argument to varargs method
//        test(new int[]{1, 2, 3});  // new Object[]{new int[]{1, 2, 3}}
//        Object[] objects = new int[]{};  // 不可以test(Arrays.stream(new int[]{1, 2, 3}).boxed().toArray());  // new Object[]{new int[]{1, 2, 3}}/** 解决方法:*  - 修改形参为int类型*  - 修改实参 Arrays.stream(new int[]{1, 2, 3}).boxed().toArray() -> 转为包装类*///        System.out.println(String.format("%d %d %d", new int[]{1, 2, 3}));  // java.util.IllegalFormatConversionException: d != [ISystem.out.println(String.format("%d %d %d", Arrays.stream(new int[]{1, 2, 3}).boxed().toArray()));}static void test(Object... objects) {  // Object[]for (Object object : objects) {System.out.println(object);}}
}
  • 情况3为改进前:

二、JDK10-11

1. var语法

示例1:类型推断

package com.scnu.v10;import java.util.Arrays;
import java.util.Comparator;public class LambdaTypeInterface {public static void main(String[] args) {Integer[] array1 = {5, 3, 1, 2};System.out.println(Arrays.toString(sort(array1, (a, b) -> a.compareTo(b))));  // 类型推断IntegerSystem.out.println(Arrays.toString(sort(array1, (var a, var b) -> a.compareTo(b))));  // JDK11中新增的var可以使用的位置System.out.println(Arrays.toString(sort(array1, Integer::compareTo)));String[] array2 = {"d", "c", "a", "b"};System.out.println(Arrays.toString(sort(array2, (a, b) -> a.compareTo(b))));  // 类型推断StringSystem.out.println(Arrays.toString(sort(array2, String::compareTo)));}static <T> T[] sort(T[] array, Comparator<T> comparator) {for (int i = 0; i < array.length - 1; i++) {for (int j = 0; j < array.length - 1 - i; j++) {if(comparator.compare(array[j], array[j + 1]) > 0) {T t = array[j];array[j] = array[j + 1];array[j + 1] = t;}}}return array;}
}

示例2:var 用在局部变量的类型推断上

package com.scnu.v10;import java.io.FileInputStream;
import java.io.IOException;
import java.util.List;
import java.util.stream.Stream;/*
局部变量类型推断*/
public class LocalVariableTypeInterface {static class Student {private String name;private Integer age;public Student() {}public Student(String name, Integer age) {this.name = name;this.age = age;}public String getName() {return name;}public void setName(String name) {this.name = name;}public Integer getAge() {return age;}public void setAge(Integer age) {this.age = age;}@Overridepublic String toString() {return "Student{" +"name='" + name + '\'' +", age=" + age +'}';}}public static void main(String[] args) throws Exception{// 所谓的类型推断,就是编译器能够推断出变量、参数的类型,无需显式设置这些变量、参数的类型// JDK 10引入的var能够用的几个位置 ↓// 局部变量的声明上var i = 10;  // intvar student = new Student("张三", 18);  // Studentvar list = List.of("a", "b", "c");  // List<String>// 增强for循环的变量上for (var key : list) {  // key为String类型System.out.println(key);}int[] array = {1, 2, 3};// 普通for循环的变量上for (var j = 0; j < array.length; j++) {System.out.println(array[j]);}try (var in = new FileInputStream("1.txt")) {// try-with-resource语法的变量声明上}}}

示例3:何时使用var

package com.scnu.v10;import java.io.ByteArrayOutputStream;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.stream.Collectors;
import java.util.stream.Stream;/*
何时该使用var*/
public class UseVar {public static void main(String[] args) {t3();}static class Student {String name;public Student() {}public Student(String name) {this.name = name;}public String getName() {return name;}public void setName(String name) {this.name = name;}}// 1. var 会影响可读性,不建议修改 -> 如果非要使用,建议让变量名起得更有意义static void t1() {List<Student> students = queryStudentsService();var studentList = queryStudentsService();}// 2. 类型已经不重要,不影响阅读时static List<Student> queryStudentsService() {try (var studentStream = queryStudentsDao()) {// 流处理,不影响代码的阅读return studentStream.filter(student -> student.name.startsWith("张")).collect(Collectors.toList());}}static Stream<Student> queryStudentsDao() {return Stream.of(new Student("张三"), new Student("李四"), new Student("张六"));}// 3. 类型已经不重要,不影响阅读时static void t3() {List<String> fruitList = List.of("apple", "banana", "apple");// Map<String, Long>var collect = fruitList.stream().collect(Collectors.groupingBy(s -> s, Collectors.counting()));  // apple 2 banana 1// 找出出现次数最多的水果 Optional<Map.Entry<String, Long>>var max = collect.entrySet().stream().max(Map.Entry.comparingByValue());System.out.println(max.map(Map.Entry::getKey).orElse(null));  // apple}// 4. 变量声明紧接着构造时static void t4() {// ByteArrayOutputStreamvar outputStream = new ByteArrayOutputStream();}// 5. 要小心static void t5() {// 1. 与 <> 符号一起使用,泛型要写var list = new ArrayList<>();  // ArrayList<Object>var list2 = new ArrayList<String>();// 2. 与泛型方法一起使用var a = List.of(1, 2, 3);  // List<Integer>var b = List.of();  // List<Object>// 3. 与字面量一起使用byte flags = 0;var flags2 = 0;         // intshort mask = 0x7fff;var mask2 = 0x7fff;     // intlong base = 17;var base2 = 17;         // int}
}

三、JDK13

新特性更新列表:JDK 13

1. ZGC增强

功能目标:

  • 允许ZGC将未使用的堆内存返回给操作系统
  • 减少长期运行的Java应用程序的内存占用

实现原理:

  • 默认情况下,ZGC会保留最大堆内存(-Xmx)供应用程序使用
  • 启用此功能后,当内存压力降低时,ZGC会自动将空闲内存返还给操作系统
  • 当应用需要更多内存时,ZGC会重新向操作系统申请

启用方式:

-XX:+UseZGC -XX:+ZUncommit -XX:ZUncommitDelay=<seconds>(默认300秒)

注意事项:

  • 不会将堆内存降到低于最小堆大小(-Xms)
  • 存在一个延迟参数(ZUncommitDelay)控制内存保持未提交状态的时间

实际意义:

  • 这项改进特别适合容器化环境,使得Java应用在负载降低时更高效地利用系统资源,避免长期占用不必要的内存。

四、JDK14

1. switch表达式

示例代码1:JDK8及之前

特点:

  • 基于case和break的语法;
  • 必须使用break,否则会穿透(fall-through)到下一个case;
  • 只能作为语句(Statement),不能返回值

问题:

  • 代码冗长,容易漏写break导致逻辑错误;
  • 不能直接返回结果,必须借助变量赋值
package com.scnu.v14;/*
switch表达式*/
public class SwitchExpressions {public static void test(int day) {switch (day) {case 1:case 2:case 3:case 4:case 5: {System.out.println("工作日");
//                break;}case 6:case 7: {System.out.println("休息日");
//                break;}default: {throw new RuntimeException("非法输入");}}}public static void main(String[] args) {test(2);}
}

示例代码2:JDK14引入的switch新语法

改进点:

  • 支持 -> 箭头语法(类似Lambda),避免break穿透;
  • 可以直接返回值(可作为表达式使用);
  • 支持 yield 显式返回值(在代码块中使用);
  • switch表达式要涵盖所有情况,如果配合枚举使用,此时可以省略default。

优势:

  • 更简洁,无需break;
  • 直接返回结果,无需额外变量。
package com.scnu.v14;/*
switch表达式*/
public class SwitchExpressions {enum Day {D1, D2, D3, D4, D5, D6, D7;}public static void test(Day day) {/*1. 用case ... -> 替代 case ... :2. case后可以同时跟多个值3. -> 跟的是表达式的执行结果4. case 要跟多行代码- 用 { }- 用 yield生成表达式结果5. switch表达式要涵盖所有情况,配合枚举使用,此时可以省略default*/String str = switch (day) {case D1, D2, D3, D4, D5 -> "工作日";case D6, D7 -> {System.out.println("It's weekend");yield "休息日";  // 使用yield返回}
//            default -> throw new RuntimeException("非法输入");};System.out.println(str);  // 休息日}public static void main(String[] args) {test(Day.D6);}
}

五、JDK15

1. TextBlock文本块

package com.scnu.v15;import java.sql.SQLOutput;
import java.util.Arrays;public class TextBlock {public static void main(String[] args) {// 1. 可读性差String json = "{\n" +"    \"name\": \"张三\",\n" +"    \"age\": 18\n" +"}";
//        System.out.println(json);// 2. 好处 - 可读性↑//  Concatenation can be replaced with text blockString json2 = """{"name": "张三","age": 18}""";
//        System.out.println(json2);String json3 = """{"name": "%s","age": %d}""".formatted("李四", 20);
//        System.out.println(json3);// 3. 注意事项1 - 换行String s1 = """abc""";  // 如果不想要字符串最后产生一个换行,把三个双引号紧接着最后一行写String s2 = """abc""";  // 如果想要字符串最后产生一个换行,把三个双引号另起一行//        System.out.println(Arrays.toString(s1.getBytes()));  // [97, 10, 98, 10, 99]
//        System.out.println(Arrays.toString(s2.getBytes()));  // [97, 10, 98, 10, 99, 10]// 4. 注意事项2 - 前空格(由最后的三个引号决定前面有多少个空格,比如下面的例子前面有1个空格)String s3 = """abc""";
//        System.out.println(Arrays.toString(s3.getBytes()));  // [32, 97, 10, 32, 98, 10, 32, 99, 10]    其中的32指空格// 5. 注意事项3 - 后空格(在空格后加上'\s')String s4 = """a   \sbc""";System.out.println(s3);System.out.println(Arrays.toString(s4.getBytes()));  // [97, 32, 32, 32, 32, 10, 98, 10, 99, 10]}
}

六、JDK16

1. record

record是Java 16引入的一种新的类声明方式,用于简化不可变数据类(Immutable Data Class)的编写。它的核心目的是替代Lombok的@Data 或 Kotlin的data class,减少样板代码(如equals()、hashCode()、toString()等)。

基本语法

public record 类名(参数列表) {// 可选:额外的方法或静态字段
}

示例:定义一个Point记录类

public record Point(int x, int y) {// 隐式包含以下内容:// 1. final 字段:private final int x; private final int y;// 2. 全参数构造方法:Point(int x, int y)// 3. 自动生成的 equals()、hashCode()、toString()// 4. 访问方法:x()、y()(注意不是 getX())
}

核心特性

(1)自动生成的成员

  • 字段:所有参数默认是private final的;
  • 构造方法:自动生成一个全参数构造方法;
  • 访问方法:生成 x()、y(),而不是传统的 getX()、getY();
  • equals() 和 hashCode():基于所有字段值比较;
  • toString():格式为 类名[属性1 = 值, 属性2 = 值]。

(2)不可变性(Immutable)-> 浅层不可变

  • 所有字段是final的,创建后不能修改;
  • 如果需要“修改”值,可以通过复制构造方式:
Point p1 = new Point(1, 2);
Point p2 = new Point(p1.x(), 100); // "修改" y 的值

(3)可以自定义方法

public record Point(int x, int y) {// 自定义方法public double distance() {return Math.sqrt(x * x + y * y);}// 静态字段public static final Point ORIGIN = new Point(0, 0);
}

(4)紧凑构造方法(Compact Constructor)

用于校验参数或计算派生值:

public record Point(int x, int y) {// 紧凑构造方法(无参数列表)public Point {if (x < 0 || y < 0) {throw new IllegalArgumentException("坐标不能为负数");}}
}
  • 此构造方法在自动生成的构造方法之后执行,可用于参数校验或调整字段值。

注意事项

  • 不能继承其它类(但可以实现接口);
  • 不能声明非final字段(所有字段由record参数决定);
  • 生成的 equals() 和 hashCode() 基于所有字段,如需特殊逻辑需手动重写;
  • 适用于纯数据载体,不适合复制业务逻辑的类。

示例2:定义一个Student类

package com.scnu.v16;public record Student(String name, Integer age/*, String[] address*/) {/*隐式包含以下内容:- final字段:private final String name; private final Integer age- 全参数构造方法:Student(String name, Integer age)- 自动生成的equals()、hashCode、toString()- 访问方法: name()、age() (注意不是getX())*/public Student {if (name == null) {throw new RuntimeException("name 不能为 null");}}//    private Integer id;  // 成员变量//    {
//        System.out.println("初始化代码块");
//    }static int i;static void foo() {}static {System.out.println("---------");}public void bar() {}@Overridepublic String toString() {return "Student{" +"name='" + name + '\'' +", age=" + age +'}';}
}

把光标放到Student类名上,按ALT + Enter,点击“convert record to class"

package com.scnu.v16;import java.util.Objects;public final class Student {/*隐式包含以下内容:- final字段:private final String name; private final Integer age- 全参数构造方法:Student(String name, Integer age)- 自动生成的equals()、hashCode、toString()- 访问方法: name()、age() (注意不是getX())*/public Student(String name, Integer age) {if (name == null) {throw new RuntimeException("name 不能为 null");}this.name = name;this.age = age;}//    private Integer id;  // 成员变量//    {
//        System.out.println("初始化代码块");
//    }static int i;private final String name;private final Integer age;static void foo() {}static {System.out.println("---------");}public void bar() {}@Overridepublic String toString() {return "Student{" +"name='" + name + '\'' +", age=" + age +'}';}public String name() {return name;}public Integer age() {return age;}@Overridepublic boolean equals(Object obj) {if (obj == this) return true;if (obj == null || obj.getClass() != this.getClass()) return false;var that = (Student) obj;return Objects.equals(this.name, that.name) &&Objects.equals(this.age, that.age);}@Overridepublic int hashCode() {return Objects.hash(name, age);}}

示例3:函数式编程

package com.scnu.v16;import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;public class TestRecord {public static void main(String[] args) {/*适用场景:1. 作为数据载体,例如 DTO, VO等2. 相对于属性可变的普通pojo,record对象更适合函数式编程*/
//        Student student = new Student("张三", 18, new String[]{"上海", "北京"});
//        student.address()[0] = "深圳";
//        System.out.println(Arrays.toString(student.address()));  // ["深圳", "北京"]//        Student student = new Student(null, 19);/*需求:把下面的学生数据- 1. 转换成java对象- 2. 保留20岁以上的- 3. 姓相同的分到一组*/String cvs = """张三,23李四,20王五,19赵六,20张四,22王八,15""";Map<Character, List<Student>> collect = Arrays.stream(cvs.split("\n")).map(line -> {String[] split = line.split(",");return new Student(split[0], Integer.parseInt(split[1]));}).filter(stu -> stu.age() >= 20).collect(Collectors.groupingBy(stu -> stu.name().charAt(0)));System.out.println(collect);  // {张=[Student{name='张三', age=23}, Student{name='张四', age=22}], 赵=[Student{name='赵六', age=20}], 李=[Student{name='李四', age=20}]}}
}

示例4:序列化

<dependency><groupId>com.fasterxml.jackson.core</groupId><artifactId>jackson-databind</artifactId><version>2.15.2</version> <!-- 或使用最新版本 -->
</dependency>
package com.scnu.v16;import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import java.io.Serializable;public class Outer {static record Inner(String name, int age) implements Serializable {}public static void main(String[] args) throws JsonProcessingException {Object obj = new Inner("张三", 20);ObjectMapper om = new ObjectMapper();System.out.println(om.writeValueAsString(obj));  // {"name":"张三","age":20}}
}

record能做的和不能做的:

  • 自己显示实现由编译器自动生成的方法,如toString等;✅
  • 静态变量和静态代码块;✅
  • 成员方法 ✅
  • 成员变量和初始化代码块 ❌
    • 成员变量只能出现在header部分
  • 实现接口 ✅
  • 泛型参数 ✅
  • 嵌套record ✅
  • 注解 ✅
  • 作为内部类,是static的
    • 意味着不能访问外部类的成员变量或成员方法 ❌
  • 序列化和反序列化,实现序列化接口即可;✅
    • 序列化仅基于record组件,反序列化仅使用它的标准构造,不能自定义序列化、反序列化规则 ❌
  • 反射获取record信息 ✅
    • Class.isRecord判断是否为record类型
    • Class.getRecordComponents获取record的所有组件(成员变量)。

record va 传统类 vs Lombok

特性record传统类 + Lombok @Data说明
不可变性✅默认❌需手动设finalrecord字段自动final
样板代码✅自动生成✅Lombok生成record无需依赖工具
构造方法✅全参数自动生成❌需手动写或用Lombok
自定义逻辑✅支持紧凑构造✅完全自定义record不能添加非final字段
继承❌不能继承其它类✅可以record隐式继承Record类

2. instanceof增强

传统 instanceof用法的问题

在Java 16之前,我们需要这样使用 instanceof:

if (obj instanceof String) {String s = (String) obj;  // 需要显式类型转换System.out.println(s.length());
}

这种方式有三个步骤:

  • 类型检查(instanceof)
  • 类型转换(强制转换)
  • 声明新变量存储转换后的对象

Java 16的模式匹配 instanceof

Java16 允许将类型检查和类型转换合并:

if (obj instanceof String s) {  // 直接声明变量sSystem.out.println(s.length());  // s自动是String类型
}

关键特性

  • 模式变量自动声明:String s直接在instanceof表达式中声明
  • 自动类型转换:不需要显示强制转换
  • 变量作用域:模式变量s只在if块内有效

作用域规则

模式变量的作用域是“流向作用域”(flow scoping):

if (!(obj instanceof String s)) {// 这里不能使用sreturn;
}
// 这里可以使用s,因为如果obj不是String,上面已经return了
System.out.println(s.length());

示例1:

package com.scnu.v16;public class InstanceOf {static abstract class Animal {}static class Dog extends Animal {int age;public Dog(int age) {this.age = age;}public void foo() {System.out.println("foo");}}static class Cat extends Animal {public void bar() {System.out.println("bar");}}// 需求:要在方法内调用子类持有方法static void test(Animal animal) {/*if (animal instanceof Dog) {((Dog) animal).foo();  // 向下转型} else  if (animal instanceof Cat) {((Cat) animal).bar();}*/// 不需要加强制类型转换// 1. 注意变量的作用范围 (if或else if语句块内)// 2. 与其它boolean表达式一起使用,必须是instanceof为真时才能使用它转换后的这个子类的变量if (animal instanceof Dog d && d.age > 1) {d.foo();} else if (animal instanceof Cat c) {c.bar();}}static void test2(Object obj)  {if (!(obj instanceof String s)) {// 这里不能使用sreturn;}// 这里可以使用s,因为如果obj不是String,上面已经return了System.out.println(s.length());}public static void main(String[] args) {Animal animal = new Cat();test(animal);  // barAnimal animal2 = new Dog(2);test(animal2);  // fooObject obj = "abc";test2(obj);  // 3Object obj2 = 123;test2(obj2);}
}

3. 局部枚举、接口和record类

具备类型指的是在方法体或代码块内部定义的类、接口、枚举或record。它们的作用域仅限于定义它们的代码块。

特点与限制

  • 作用域限制
    • 只在定义它们的方法或代码块内可见
    • 不能从外部访问
  • 访问权限
    • 不能有访问修饰符(public/private等)
    • 隐式为final,不能被继承
  • 其它限制
    • 不能包含静态成员(除了常量变量)
    • 不能定义在静态上下文中(如静态方法)的局部接口

示例:

package com.scnu.v16;public class LocalEnumInterfaceRecord {public static void main(String[] args) {// 局部枚举类enum Color {RED, BLUE;}for (Color color : Color.values()) {System.out.println(color);  // RED BLUE}// 局部接口interface MyInterface {void foo();}MyInterface myInterface = () -> System.out.println("foo...");myInterface.foo();  // foo...// 局部record类record MyRecord(int age) {}MyRecord myRecord = new MyRecord(20);System.out.println(myRecord.age);  // 20}
}

七、JDK21

1. record patterns

Java 21引入了Record Patterns,是对Java模式匹配功能的进一步扩展,专门用于结构record类的实例。这个特性与Pattern Matching for instanceof 和 Pattern Matching for switch共同构成了Java的模式匹配体系。

基本概念

Record Patterns允许你直接将reocrd对象的组件解构为变量,无需手动调用访问方法。

  • 传统方式(Java 16之前):
record Point(int x, int y) {}static void printSum(Object obj) {if (obj instanceof Point) {Point p = (Point) obj;System.out.println(p.x() + p.y());}
}
  • Record Patterns(Java 21):
static void printSum(Object obj) {if (obj instanceof Point(int x, int y)) {  // 直接解构System.out.println(x + y);  // 直接使用x和y}
}

核心特性

  • 基本解构语法:
record Point(int x, int y) {}// 使用Record Pattern
if (obj instanceof Point(int x, int y)) {// 可以直接使用x和y
}
  • 嵌套解构:

Record Patterns支持嵌套解构,可以一次解构多层record

record Point(int x, int y) {}
record Circle(Point center, double radius) {}static void printComponents(Object obj) {if (obj instanceof Circle(Point(var x, var y), var r)) {System.out.printf("Center: (%d, %d), Radius: %.2f%n", x, y, r);}
}
  • 与var结合使用:

可以使用var让编译器推断类型:

if (obj instanceof Point(var x, var y)) {// x和y的类型会被推断为int
}
  • 在switch中使用:

Java21中Record Patterns可以与switch表达式完美结合

record Login(String username, String password) {}
record Token(String token) {}String processAuth(Object auth) {return switch (auth) {case Login(var user, var pass) -> "Login with " + user;case Token(var t) -> "Token: " + t;default -> "Unknown auth type";};
}

高级用法

  • 部分解构

可以只解构你关心的部分组件:

record Person(String name, int age, String address) {}if (obj instanceof Person(var name, var age, _)) {// 只使用name和age,忽略address
}
  • 类型模式与Record Patterns组合
if (obj instanceof String s && s.length() > 0) {// 字符串处理
} else if (obj instanceof Point(var x, var y) && x == y) {// 处理x等于y的点
}
  • 守卫模式(Guarded Patterns)

可以结合条件表达式使用:

switch (obj) {case Point(var x, var y) when x > y -> System.out.println("x > y");case Point(var x, var y) when x < y -> System.out.println("x < y");case Point(var x, var y) -> System.out.println("x == y");// ...
}

实现原理

Record Patterns在编译时会被转换为:

  • 类型检查
  • 组件值的提取(调用对应的访问方法)
  • 变量的绑定

例如:

if (obj instanceof Point(int x, int y))

大致会被转换为:

if (obj instanceof Point) {Point $p = (Point) obj;int x = $p.x();int y = $p.y();// 后续代码
}

使用场景

  • 数据解析:处理复杂的数据结构
record Customer(String name, Address address) {}
record Address(String street, String city) {}if (obj instanceof Customer(var name, Address(var street, _))) {System.out.println(name + " lives on " + street);
}
  • API响应处理
switch (apiResponse) {case Success(var data) -> processData(data);case Error(var code, var message) -> handleError(code, message);
}
  • 模式匹配算法
static int eval(Expr e) {return switch (e) {case Add(Expr l, Expr r) -> eval(l) + eval(r);case Mul(Expr l, Expr r) -> eval(l) * eval(r);case Const(int i) -> i;};
}

限制与注意事项

  • 只能用于record类型
  • 解构的模式变量是隐式final的
  • 嵌套结构可能会影响可读性,需适度使用
  • 需要Java 21或更高版本。

示例1:嵌套解构

package com.scnu.v21;public class TestRecordPattern {record Student(String name, int age, Address address) {}record Address(String city, String street) {}static void test(Object object) {if (object instanceof  Student student) {System.out.println(student.name + " " + student.age);}// record patternif (object instanceof  Student(String name, int age, Address(String city, String street))) {System.out.println(name + " " + age + " " + city + " " + street);  // 张三 19 北京, 回龙观111号}}public static void main(String[] args) {test(new Student("张三", 19, new Address("北京", "回龙观111号")));}
}

2. pattern matching for switch

之前switch能够匹配的类型:byte short char int String enum

新语法

switch(object) {

        case 普通类型 变量名 [ when 条件 ] -> ...;

        case record类型 变量名 -> ...;

        case enum类型 变量名 -> ...;

        case null -> ...;

        default -> ...;

}

注意:变量名不能省略。

示例代码:

package com.scnu.v21;public class TestSwitch {public static void main(String[] args) {test1(null);  // 其它类型或nulltest1("abc");  // 其它类型或nulltest1(new A("张三"));  // 类型A张三test1(new B("新特性"));  // 类型B新特性test2(new Student("李四", 21));  // 李四 21test3(Day.D1);  // 工作日test3(Day.D2);  // 工作日test3(Day.D3);  // 工作日test3(Day.D4);  // 工作日test3(Day.D5);  // 工作日test3(Day.D6);  // 休息日test3(Day.D7);  // 休息日test3(1);  // 非法输入test4(new Student("张三", 28));  // defaulttest4(new Student("李四", 30));  // 李四 30}// -----------------------------------enum Day {  // 枚举类D1, D2, D3, D4, D5, D6, D7;  // 枚举对象}/*配合枚举类型使用- enum对象和enum类可以混用- enum对象需要放在enum类之前(否则有前支匹配错误) -> 范围小的出现在上面,范围大的出现在下面- enum对象不能再声明变量(新语法才可以)- 如果能穷举,则可以省略default(传过来的就是一个枚举类型,如 Day obj)*/static void test3(Object obj) {switch (obj) {case Day.D6, Day.D7 -> System.out.println("休息日");  // 旧语法 枚举对象case Day d -> System.out.println("工作日");  // 新语法  枚举类 -> enum对象需要放在enum类之前(否则有前支匹配错误)default -> System.out.println("非法输入");}}// ------------------------------------------record Student(String name, int age) {}/*when 条件*/static void test4(Object obj) {switch (obj) {case Student(String name, int age) when age >= 30 -> System.out.println(name + " " + age);default -> System.out.println("default");}}/*配合record类型使用*/static void test2(Object obj) {switch (obj) {
//            case Student stu -> System.out.println(stu.name + " " + stu.age);case Student(String name, int age) -> System.out.println(name + " " + age);default -> System.out.println("default");}}// ------------------------------------static void test1(Object obj) {/*if (obj instanceof A a) {System.out.println("类型A");} else if (obj instanceof B b) {System.out.println("类型B");}*/switch (obj) {
//            case null -> System.out.println("null值");  // 如果没有加这一项,传入了null值会抛空指针异常case A a -> System.out.println("类型A" + a.name);case B b -> System.out.println("类型B" + b.title);case null, default -> System.out.println("其它类型或null");}}static class A {String name;public A(String name) {this.name = name;}}static class B {String title;public B(String title) {this.title = title;}}
}

3. 虚拟线程

JDK 21正式引入了虚拟线程(Virtual Threads),是Java并发编程的重大革新,旨在简化高吞吐量并发应用的开发,并显著提升可扩展性。

(1)虚拟线程是什么

虚拟线程是轻量级线程,由JVM管理,而不是操作系统。它们与传统平台线程(java.lang.Thread)的关键区别在于:

特性平台线程(Platform Thread)虚拟线程(Virtual Thread)
调度方式由OS线程调度(1:1模型)由JVM调度(M:N模型)
内存开销每个线程 ~ 1MB栈内存初始仅 ~ 几百字节
创建成本昂贵(受OS线程数限制)廉价(可创建数百万个)
阻塞代价高(占用OS线程)低(JVM自动挂起/恢复)
适用场景CPU密集型任务I/O密集型、高并发任务

(2)为什么需要虚拟线程

传统线程模型的痛点

  • 线程创建成本高:每个Thread对应一个OS线程,数量受限(通常几千个);
  • 阻塞操作浪费资源:如数据库查询、HTTP请求等I/O操作会阻塞线程,导致线程池耗尽;
  • 异步编程复杂:CompletableFuture或响应式编程(如Reactor)代码难以维护。

虚拟线程的优势

  • 高并发:轻松创建数百万个虚拟线程,适合微服务、Web服务器等场景;
  • 同步编程模型:无需回调地狱,直接用Thread和ExecutorService编写同步代码;
  • 兼容现有代码:虚拟线程是Thread的子类,现有API(如synchronized、ThreadLocal)直接支持。

(3)如何使用虚拟线程

创建虚拟线程

  • 方式1:Thread.startVirtualThread(最简单)
Thread.startVirtualThread(() -> {System.out.println("Hello, Virtual Thread!");
});
  • 方式2:Thread.ofVirtual(更灵活)
Thread virtualThread = Thread.ofVirtual().name("my-virtual-thread").start(() -> {System.out.println("Running in virtual thread");});
  • 方式3:Executors.newVirtualThreadPerTaskExecutor(推荐)
try (var executor = Executors.newVirtualThreadPerTaskExecutor()) {executor.submit(() -> System.out.println("Task 1"));executor.submit(() -> System.out.println("Task 2"));
} // 自动关闭

对比传统线程池

// 传统线程池(固定线程数)
ExecutorService executor = Executors.newFixedThreadPool(200);// 虚拟线程池(每个任务一个虚拟线程)
ExecutorService virtualExecutor = Executors.newVirtualThreadPerTaskExecutor();
  • 传统线程池受限于线程数(如200),而虚拟线程可处理百万并发任务;
  • 虚拟线程在I/O阻塞时自动释放OS线程,不会浪费资源。

(4)虚拟线程的工作原理

JVM调度机制

  • 挂起(yield):当虚拟线程执行I/O或LockSupport.park()时,JVM将其挂起,释放OS线程;
  • 恢复(Resume):当I/O完成,JVM重新调度虚拟线程到可用OS线程继续执行。

(5)适用场景 vs 不适用场景

适用场景

  • Web服务器(如Tomcat、Spring Boot)处理高并发请求;
  • 数据库访问(JDBC阻塞操作);
  • 微服务调用(HTTP/RPC请求);
  • 文件/网络I/O 密集型任务。

不适用场景

  • CPU密集型计算(虚拟线程不会提升计算性能);
  • 依赖ThreadLocal的复杂场景(需注意内存泄露)。

(6)注意事项

  • 不用池化虚拟线程:虚拟线程是轻量级的,每次任务新建一个即可;
  • 避免synchronized阻塞:改用ReentrantLock,否则会固定占用OS线程;
  • ThreadLocal慎用:虚拟线程可能会大量创建,导致内存泄露。

http://www.lqws.cn/news/187057.html

相关文章:

  • React源码阅读-fiber核心构建原理
  • 【数据分析】R版IntelliGenes用于生物标志物发现的可解释机器学习
  • QPair 类说明
  • 水库大坝安全监测系统是什么?需要用到哪些设备?
  • 1.3 古典概型和几何概型
  • 2025.6.5学习日记 Nginx主目录文件 .conf介绍、热部署 定时日志切割
  • 实战设计模式之模板方法模式
  • Go 中的 Map 与字符处理指南
  • 如何使用Webhook触发器,在 ONLYOFFICE 协作空间构建智能工作流
  • C++中的概念(Concepts)
  • 自然语言处理的发展
  • 数字孪生恰似企业的“智能军师”,精准助力决策
  • 【python基础知识】 *args, **kwargs介绍
  • 一篇文章实现Android图片拼接并保存至相册
  • 深入了解linux系统—— 进程池
  • Redis哨兵模式
  • CSS 性能优化
  • 微信小程序动态效果实战指南:从悬浮云朵到丝滑列表加载
  • 密码学基础——SM4算法
  • spring重试机制
  • 一种全新的非对称加密算法
  • 从 GreenPlum 到镜舟数据库:杭银消费金融湖仓一体转型实践
  • 金融系统渗透测试
  • 交易所系统攻坚:高并发撮合引擎与合规化金融架构设计
  • pe文件结构(TLS)
  • 字节推出统一多模态模型 BAGEL,GPT-4o 级的图像生成能力直接开源了!
  • Linux(线程控制)
  • python八股文算法:三数之和
  • 实践提炼,EtherNet/IP转PROFINET网关实现乳企数字化工厂增效
  • 正则持续学习呀