sealed关键字
在Java 15(预览)/ Java 17(正式)中,sealed关键字的引入为Java的类型系统带来了革命性的变化。它允许开发者精确控制哪些类可以继承或实现某个类型,在开放性和封闭性之间找到了完美的平衡点。
为什么需要sealed?
传统的Java类继承机制存在一个两难困境:
- 使用
final:完全禁止继承,过于严格 - 不添加限制:任何人都可以继承,可能导致滥用
java
// 传统方案的问题
public class Animal { } // 任何人都可以继承
public final class Cat extends Animal { } // 完全禁止继承
在实际开发中,我们常常需要一种受控的继承 :明确知道哪些子类存在,同时允许这些子类自由扩展。这正是sealed关键字要解决的问题。
sealed类基础语法
基本声明
使用sealed关键字声明一个类或接口,并通过permits子句列出允许的子类型:
java
// 定义sealed类,只允许Dog和Cat继承
public sealed class Animal permits Dog, Cat {
protected String name;
public Animal(String name) {
this.name = name;
}
public abstract String makeSound();
}
// 子类可以是final
final class Dog extends Animal {
public Dog(String name) {
super(name);
}
@Override
public String makeSound() {
return "Woof!";
}
}
// 子类可以是非sealed(允许进一步继承)
non-sealed class Cat extends Animal {
public Cat(String name) {
super(name);
}
@Override
public String makeSound() {
return "Meow!";
}
}
// 子类也可以是sealed
sealed class Bird extends Animal permits Sparrow, Eagle {
public Bird(String name) {
super(name);
}
}
子类的三种修饰符⭐
sealed类的子类必须声明以下三种修饰符之一:
| 修饰符 | 含义 | 适用场景 |
|---|---|---|
final |
禁止进一步继承 | 叶子节点类 |
sealed |
继续受限继承 | 需要进一步细分的中间类 |
non-sealed |
恢复开放性 | 允许任意扩展 |
java
// 完整的继承层次示例
public sealed class Shape permits Circle, Rectangle, Triangle {
public abstract double area();
}
// final:不能再被继承
final class Circle extends Shape {
private double radius;
public Circle(double radius) {
this.radius = radius;
}
@Override
public double area() {
return Math.PI * radius * radius;
}
}
// sealed:继续受限继承
sealed class Rectangle extends Shape permits Square {
protected double width;
protected double height;
public Rectangle(double width, double height) {
this.width = width;
this.height = height;
}
@Override
public double area() {
return width * height;
}
}
final class Square extends Rectangle {
public Square(double side) {
super(side, side);
}
}
// non-sealed:恢复开放性
non-sealed class Triangle extends Shape {
private double base;
private double height;
public Triangle(double base, double height) {
this.base = base;
this.height = height;
}
@Override
public double area() {
return 0.5 * base * height;
}
}
// Triangle是non-sealed,所以可以任意继承
class RightTriangle extends Triangle {
public RightTriangle(double base, double height) {
super(base, height);
}
}
sealed接口
sealed同样适用于接口,这在定义领域模型时尤为有用:
java
// 定义sealed接口
public sealed interface PaymentMethod
permits CreditCard, PayPal, WeChatPay {
void pay(BigDecimal amount);
String getTransactionId();
}
// 实现类可以是record(record默认final)
record CreditCard(String cardNumber, String cvv)
implements PaymentMethod {
@Override
public void pay(BigDecimal amount) {
System.out.println("支付 " + amount + " 元,使用信用卡: " + cardNumber);
}
@Override
public String getTransactionId() {
return "CC_" + UUID.randomUUID();
}
}
record PayPal(String email) implements PaymentMethod {
@Override
public void pay(BigDecimal amount) {
System.out.println("支付 " + amount + " 元,使用PayPal: " + email);
}
@Override
public String getTransactionId() {
return "PP_" + UUID.randomUUID();
}
}
non-sealed class WeChatPay implements PaymentMethod {
private String openId;
public WeChatPay(String openId) {
this.openId = openId;
}
@Override
public void pay(BigDecimal amount) {
System.out.println("支付 " + amount + " 元,使用微信支付: " + openId);
}
@Override
public String getTransactionId() {
return "WX_" + System.currentTimeMillis();
}
}
与模式匹配的完美配合
sealed类与Java的模式匹配(Pattern Matching)是天作之合。由于编译器知道所有可能的子类型,可以实现穷举性检查:
java
public class PaymentProcessor {
// 使用模式匹配处理支付
public static String processPayment(PaymentMethod payment) {
// 编译器可以验证所有情况都已覆盖
return switch (payment) {
case CreditCard cc ->
String.format("处理信用卡支付: %s", cc.cardNumber());
case PayPal pp ->
String.format("处理PayPal支付: %s", pp.email());
case WeChatPay wp ->
String.format("处理微信支付: %s", "交易完成");
};
}
// 处理sealed类的继承层次
public static double calculateArea(Shape shape) {
return switch (shape) {
case Circle c -> Math.PI * c.radius() * c.radius();
case Rectangle r -> r.width() * r.height();
case Square s -> s.side() * s.side();
case Triangle t -> 0.5 * t.base() * t.height();
};
}
}
实际应用场景
领域驱动设计(DDD)中的值对象
java
// 定义货币类型,只允许特定的币种
public sealed interface Currency permits USD, EUR, CNY, JPY {
String getCode();
String getSymbol();
}
record USD() implements Currency {
@Override
public String getCode() { return "USD"; }
@Override
public String getSymbol() { return "$"; }
}
record EUR() implements Currency {
@Override
public String getCode() { return "EUR"; }
@Override
public String getSymbol() { return "€"; }
}
record CNY() implements Currency {
@Override
public String getCode() { return "CNY"; }
@Override
public String getSymbol() { return "¥"; }
}
record JPY() implements Currency {
@Override
public String getCode() { return "JPY"; }
@Override
public String getSymbol() { return "¥"; }
}
表达式求值器
java
// 定义表达式类型,只允许三种表达式
public sealed interface Expression
permits Constant, Addition, Multiplication {
double evaluate();
}
record Constant(double value) implements Expression {
@Override
public double evaluate() {
return value;
}
}
record Addition(Expression left, Expression right) implements Expression {
@Override
public double evaluate() {
return left.evaluate() + right.evaluate();
}
}
record Multiplication(Expression left, Expression right) implements Expression {
@Override
public double evaluate() {
return left.evaluate() * right.evaluate();
}
}
// 使用示例
public class Calculator {
public static void main(String[] args) {
// 表达式: (3 + 4) * 5
Expression expr = new Multiplication(
new Addition(new Constant(3), new Constant(4)),
new Constant(5)
);
System.out.println("计算结果: " + expr.evaluate()); // 35.0
}
}
JSON值类型建模
java
// 定义JSON值的所有可能类型
public sealed interface JsonValue
permits JsonNull, JsonBoolean, JsonNumber, JsonString, JsonArray, JsonObject {
}
record JsonNull() implements JsonValue {}
record JsonBoolean(boolean value) implements JsonValue {}
record JsonNumber(double value) implements JsonValue {}
record JsonString(String value) implements JsonValue {}
record JsonArray(List<JsonValue> values) implements JsonValue {}
record JsonObject(Map<String, JsonValue> properties) implements JsonValue {}
// 类型安全的JSON解析器
public class JsonParser {
public static String toJsonString(JsonValue value) {
return switch (value) {
case JsonNull() -> "null";
case JsonBoolean(var b) -> Boolean.toString(b);
case JsonNumber(var n) -> Double.toString(n);
case JsonString(var s) -> "\"" + s + "\"";
case JsonArray(var arr) -> {
var elements = arr.stream()
.map(JsonParser::toJsonString)
.collect(Collectors.joining(","));
yield "[" + elements + "]";
}
case JsonObject(var props) -> {
var entries = props.entrySet().stream()
.map(e -> "\"" + e.getKey() + "\":" + toJsonString(e.getValue()))
.collect(Collectors.joining(","));
yield "{" + entries + "}";
}
};
}
}
约束与限制
包内可见性规则
permits子句中列出的类必须在同一个模块中,或者与sealed类在同一个包内:
java
// 同一个包内
package com.example.shapes;
public sealed class Shape permits Circle, Rectangle {
// ...
}
// 必须在同一个包内
final class Circle extends Shape { }
final class Rectangle extends Shape { }
不能与final同时使用
sealed和final是互斥的修饰符:
java
// ❌ 错误:sealed和final不能同时使用
public sealed final class MyClass { }
// ✅ 正确:sealed类可以有final子类
public sealed class MyClass permits SubClass { }
final class SubClass extends MyClass { }
与传统模式的对比
| 特性 | 传统继承 | sealed类 |
|---|---|---|
| 子类控制 | 无限制 | 精确控制 |
| 模式匹配穷举 | 无法保证 | 编译器保证 |
| API设计 | 容易暴露实现 | 明确公开契约 |
| 向后兼容性 | 难以限制 | 通过permits控制 |
最佳实践
在API设计中优先使用sealed
java
// 定义公开的API
public sealed interface Result<T>
permits Success, Failure {
T getValue();
}
record Success<T>(T data) implements Result<T> {
@Override
public T getValue() {
return data;
}
}
record Failure<T>(String errorMessage) implements Result<T> {
@Override
public T getValue() {
throw new IllegalStateException("Cannot get value from failure");
}
}
结合record使用
record与sealed的配合是天作之合:
java
public sealed interface HttpRequest
permits GetRequest, PostRequest, PutRequest, DeleteRequest {
String path();
Map<String, String> headers();
}
record GetRequest(
String path,
Map<String, String> headers
) implements HttpRequest {}
record PostRequest(
String path,
Map<String, String> headers,
String body
) implements HttpRequest {}
record PutRequest(
String path,
Map<String, String> headers,
String body
) implements HttpRequest {}
record DeleteRequest(
String path,
Map<String, String> headers
) implements HttpRequest {}
使用non-sealed的时机
只在确实需要让第三方扩展时才使用non-sealed:
java
// 框架提供的抽象类,允许用户扩展
public sealed class FrameworkPlugin
permits DatabasePlugin, CachePlugin, non-sealed UserPlugin {
public abstract void initialize();
}
// 允许用户自定义扩展
public class CustomPlugin extends FrameworkPlugin {
@Override
public void initialize() {
// 用户自定义逻辑
}
}
sealed不是要取代传统的继承,而是在开放和封闭之间提供了更精细的控制粒度。在Java 17及以后的版本中,sealed类与record、模式匹配等特性共同构成了现代Java函数式编程的基础,让Java语言在保持面向对象特性的同时,向更安全、更简洁的方向持续演进。