引言
Java 11 到 Java 17 是一个 LTS(长期支持)到另一个 LTS 的演进区间,引入了多项改变编码习惯的重要特性。这些特性不是零散的语法糖,而是一整套组合拳,正在重塑 Java 的编码风格。
一、局部变量类型推断(var)
1.1 基本用法
Java 10 引入 var,用于局部变量的类型推断:
java
// 编译器根据右侧推断类型
var name = "Alice"; // String
var age = 30; // int
var list = new ArrayList<String>(); // ArrayList<String>
var stream = list.stream(); // Stream<String>
var map = Map.of("a", 1, "b", 2); // Map<String, Integer>
1.2 适用范围
java
// ✅ 局部变量
var list = new ArrayList<String>();
// ✅ for-each
for (var item : list) {
System.out.println(item);
}
// ✅ for 循环
for (var i = 0; i < 10; i++) { ... }
// ✅ try-with-resources
try (var input = new FileInputStream("data.txt")) { ... }
// ❌ 方法参数
public void foo(var x) { } // 编译错误
// ❌ 返回类型
public var bar() { return 1; } // 编译错误
// ❌ 字段
private var name = "Alice"; // 编译错误
// ❌ 未初始化
var x; // 编译错误
// ❌ null 赋值
var x = null; // 编译错误,无法推断类型
1.3 最佳实践
java
// ✅ 右侧类型明显时使用 var
var stream = users.stream().filter(u -> u.isActive()).map(User::getName);
var map = new ConcurrentHashMap<String, List<Order>>();
var path = Paths.get("/data/config.json");
// ❌ 右侧类型不明显时不要用 var
var result = process(data); // process 返回什么?不清楚
var value = getData(); // getData 返回什么?不清楚
// ✅ 工厂方法返回类型清晰
var list = List.of("a", "b", "c"); // List<String>
var map = Map.of("k1", 1, "k2", 2); // Map<String, Integer>
// ✅ 简化泛型声明
// Before
Map<String, List<Map<String, Integer>>> data = new HashMap<>();
// After
var data = new HashMap<String, List<Map<String, Integer>>>();
二、Record(记录类)
2.1 基本用法
Java 14 预览,Java 16 正式发布。Record 是一种不可变数据载体:
java
// 一行定义一个不可变数据类
public record Point(int x, int y) {}
// 等价于:
public final class Point extends Record {
private final int x;
private final int y;
public Point(int x, int y) {
this.x = x;
this.y = y;
}
public int x() { return x; }
public int y() { return y; }
@Override
public boolean equals(Object o) { ... }
@Override
public int hashCode() { ... }
@Override
public String toString() { return "Point[x=" + x + ", y=" + y + "]"; }
}
2.2 自定义构造器
java
public record Range(int min, int max) {
// 紧凑构造器:验证参数
public Range {
if (min > max) {
throw new IllegalArgumentException("min > max");
}
}
}
// 带默认值的构造器
public record User(String name, int age, String role) {
public User(String name, int age) {
this(name, age, "user");
}
}
2.3 添加方法
java
public record Point(int x, int y) {
// 实例方法
public double distanceTo(Point other) {
return Math.sqrt(Math.pow(x - other.x, 2) + Math.pow(y - other.y, 2));
}
// 静态工厂方法
public static Point origin() {
return new Point(0, 0);
}
}
2.4 实现接口
java
public record Point(int x, int y) implements Comparable<Point> {
@Override
public int compareTo(Point other) {
int cmp = Integer.compare(x, other.x);
return cmp != 0 ? cmp : Integer.compare(y, other.y);
}
}
2.5 Record 与 JSON 序列化
java
// Jackson 2.12+ 原生支持 Record
ObjectMapper mapper = new ObjectMapper();
Point point = mapper.readValue("{\"x\":1,\"y\":2}", Point.class);
String json = mapper.writeValueAsString(point);
2.6 Record 的限制
| 限制 | 说明 |
|---|---|
| 不可变 | 字段是 final 的 |
| 不能继承 | 隐式 final,不能 extends |
| 不能被继承 | 不能作为父类 |
| 不能声明实例字段 | 只能有紧凑构造器中的逻辑 |
| 字段不能是可变的 | 不能有 setter |
三、Sealed Class(密封类)
3.1 基本用法
Java 15 预览,Java 17 正式发布。密封类限制哪些类可以继承/实现它:
java
// 密封接口:只允许三种形状
public sealed interface Shape permits Circle, Rectangle, Triangle {}
public record Circle(double radius) implements Shape {}
public record Rectangle(double width, double height) implements Shape {}
public record Triangle(double a, double b, double c) implements Shape {}
3.2 密封类规则
java
// 子类必须在同一个模块/包中
// 子类必须是 final、sealed 或 non-sealed
sealed interface Shape permits Circle, Rectangle, Quadrilateral {}
// Circle: final → 不能再被继承
final record Circle(double radius) implements Shape {}
// Rectangle: sealed → 可以继续限制子类
sealed record Rectangle(double width, double height) implements Shape
permits Square {}
final record Square(double side) extends Rectangle(side, side) {}
// Quadrilateral: non-sealed → 任何人都可以继承
non-sealed class Quadrilateral implements Shape {}
// 任何类都可以继承 Quadrilateral
3.3 密封类的价值
没有密封类:
├── switch 必须加 default → 编译器不知道是否穷举
├── if-else 无法保证完整性 → 新增子类可能被遗漏
└── 类型层次是开放的 → 无法控制继承
有密封类:
├── switch 不需要 default → 编译器检查穷举
├── 新增子类必须修改 permits → 编译器提醒所有 switch
└── 类型层次是封闭的 → 可控的领域建模
四、Pattern Matching(模式匹配)
4.1 instanceof 模式匹配
Java 14 预览,Java 16 正式发布:
java
// Before:先检查再转换
if (obj instanceof String) {
String s = (String) obj;
System.out.println(s.length());
}
// After:检查+绑定变量
if (obj instanceof String s) {
System.out.println(s.length()); // 直接使用 s
}
// 带条件的使用
if (obj instanceof String s && s.length() > 5) {
System.out.println(s.toUpperCase());
}
4.2 switch 模式匹配(Java 17 预览,Java 21 正式)
java
// Before:繁琐的 if-else
static String format(Object obj) {
if (obj instanceof Integer) {
return "int: " + ((Integer) obj).intValue();
} else if (obj instanceof Long) {
return "long: " + ((Long) obj).longValue();
} else if (obj instanceof String) {
return "string: " + ((String) obj).length();
} else {
return "unknown";
}
}
// After:switch 模式匹配
static String format(Object obj) {
return switch (obj) {
case Integer i -> "int: " + i;
case Long l -> "long: " + l;
case String s -> "string: " + s.length();
case null -> "null";
default -> "unknown";
};
}
4.3 带守卫条件的 switch(Java 21)
java
static String categorize(Object obj) {
return switch (obj) {
case String s when s.length() > 10 -> "long string: " + s;
case String s -> "short string: " + s;
case Integer i when i > 0 -> "positive: " + i;
case Integer i -> "non-positive: " + i;
default -> "other";
};
}
4.4 Record 模式匹配(Java 21)
java
// 解构 Record
record Point(int x, int y) {}
static void print(Object obj) {
switch (obj) {
case Point(int x, int y) -> System.out.println("Point(" + x + ", " + y + ")");
default -> System.out.println("Other");
}
}
// 嵌套解构
record Line(Point start, Point end) {}
static void printLine(Object obj) {
switch (obj) {
case Line(Point(int x1, int y1), Point(int x2, int y2)) ->
System.out.printf("Line from (%d,%d) to (%d,%d)%n", x1, y1, x2, y2);
default -> System.out.println("Other");
}
}
4.5 密封类 + switch 模式匹配
java
sealed interface Shape permits Circle, Rectangle, Triangle {}
record Circle(double radius) implements Shape {}
record Rectangle(double width, double height) implements Shape {}
record Triangle(double a, double b, double c) implements Shape {}
// 编译器知道所有子类,无需 default
static double area(Shape shape) {
return switch (shape) {
case Circle(var r) -> Math.PI * r * r;
case Rectangle(var w, var h) -> w * h;
case Triangle(var a, var b, var c) -> {
double s = (a + b + c) / 2;
yield Math.sqrt(s * (s - a) * (s - b) * (s - c));
}
};
}
五、其他重要特性
5.1 文本块(Java 13 预览,Java 15 正式)
java
// Before
String json = "{\n" +
" \"name\": \"Alice\",\n" +
" \"age\": 30\n" +
"}";
// After
String json = """
{
"name": "Alice",
"age": 30
}
""";
// SQL
String sql = """
SELECT u.name, o.total
FROM users u
JOIN orders o ON u.id = o.user_id
WHERE u.active = true
""";
5.2 switch 表达式(Java 12 预览,Java 14 正式)
java
// 语句 → 表达式,有返回值
String result = switch (day) {
case MONDAY, FRIDAY, SUNDAY -> "relax";
case TUESDAY -> "work";
case WEDNESDAY, THURSDAY -> "busy";
case SATURDAY -> "play";
};
// yield 在块中返回值
int numLetters = switch (name) {
case "Alice" -> 5;
case "Bob" -> {
System.out.println("Hi Bob!");
yield 3;
}
default -> name.length();
};
5.3 Helpful NullPointerExceptions(Java 14)
java
// Before
Exception in thread "main" java.lang.NullPointerException
// After:指出哪个变量为 null
Exception in thread "main" java.lang.NullPointerException:
Cannot invoke "String.length()" because "user.name" is null
5.4 String 新方法
java
// Java 11
" hello ".strip(); // "hello"(Unicode 感知)
" hello ".stripLeading(); // "hello "
" hello ".stripTrailing();// " hello"
"hello".repeat(3); // "hellohellohello"
"hello".isBlank(); // false
"\n\n".lines().count(); // 0(空行不计)
// Java 12
"hello".indent(4); // " hello\n"
" hello".stripIndent(); // "hello"
// Java 12
String formatted = "Name: %s, Age: %d".formatted("Alice", 30);
5.5 HttpClient(Java 11 正式)
java
HttpClient client = HttpClient.newHttpClient();
// GET
HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create("https://api.example.com/data"))
.header("Accept", "application/json")
.GET()
.build();
HttpResponse<String> response = client.send(request, HttpResponse.BodyHandlers.ofString());
// POST
HttpRequest postRequest = HttpRequest.newBuilder()
.uri(URI.create("https://api.example.com/data"))
.header("Content-Type", "application/json")
.POST(HttpRequest.BodyPublishers.ofString("{\"name\":\"Alice\"}"))
.build();
// 异步
CompletableFuture<HttpResponse<String>> future =
client.sendAsync(request, HttpResponse.BodyHandlers.ofString());
5.6 Files 新方法
java
// Java 11
Path path = Paths.get("data.txt");
String content = Files.readString(path);
Files.writeString(path, "hello world");
// 指定编码
String content = Files.readString(path, StandardCharsets.UTF_8);
六、特性组合实战
6.1 领域建模:密封类 + Record
java
// 支付方式建模
sealed interface PaymentMethod permits CreditCard, DebitCard, BankTransfer, Cash {}
record CreditCard(String number, String expiry, String cvv) implements PaymentMethod {}
record DebitCard(String number, String bank) implements PaymentMethod {}
record BankTransfer(String account, String routingNumber) implements PaymentMethod {}
record Cash() implements PaymentMethod {}
// 处理逻辑:switch 模式匹配
String describe(PaymentMethod method) {
return switch (method) {
case CreditCard(var num, var exp, _) -> "Credit card ending in " + num.substring(num.length() - 4);
case DebitCard(var num, var bank) -> "Debit card from " + bank;
case BankTransfer(var acct, var rt) -> "Bank transfer from " + acct;
case Cash -> "Cash payment";
};
}
6.2 结果类型:密封类 + Record + var
java
sealed interface Result<T> permits Success, Failure {}
record Success<T>(T value) implements Result<T> {}
record Failure<T>(String error) implements Result<T> {}
var result = fetchData();
var output = switch (result) {
case Success(var data) -> "Got: " + data;
case Failure(var err) -> "Error: " + err;
};
总结
| 特性 | Java 版本 | 核心价值 |
|---|---|---|
| var | 10 | 减少样板代码,局部变量类型推断 |
| switch 表达式 | 14 | switch 有返回值,箭头语法 |
| 文本块 | 15 | 多行字符串,告别拼接 |
| Record | 16 | 不可变数据载体,一行替代几十行 |
| Sealed Class | 17 | 封闭类型层次,可控继承 |
| Pattern Matching for instanceof | 16 | 消除强制转换 |
| Pattern Matching for switch | 21 | 类型匹配 + 守卫条件 + Record 解构 |
这些特性组合使用,正在让 Java 走向更声明式、更安全的编程风格。