**题目:Java 17 新特性——密封类(Sealed Classes)实战**

在 Java 17 中,密封类(Sealed Classes)正式成为语言规范的一部分,旨在帮助开发者更好地控制类的继承结构。本文将从概念讲起,逐步演示如何在实际项目中使用密封类,实现更安全、更易维护的代码。


一、密封类到底是什么?

密封类是对类继承层次进行限制的一种机制。与传统的 final 类不同,密封类允许你指定一组合法的子类或实现接口。被声明为密封类的类或接口只能被这些指定的子类继承或实现,其他任何类都无法扩展。

public sealed interface Shape permits Circle, Square, Rectangle {
    double area();
}

上面代码中,Shape 只允许 CircleSquareRectangle 这三个类实现。任何尝试继承或实现 Shape 的其它类都会导致编译错误。


二、为什么要使用密封类?

  1. 控制继承结构
    通过明确声明哪些类可以继承,减少潜在的误用或误解。
  2. 增强类型安全
    编译器能够在编译期发现非法的继承关系,提升代码安全性。
  3. 改进代码可读性
    代码阅读者一眼就能知道合法的子类,理解类层次结构更加清晰。
  4. 利于 pattern matching
    结合 Java 20 的 sealed classrecord,可以更好地配合 switch 表达式的 pattern matching。

三、实际使用案例

下面以一个简单的支付系统为例,演示如何使用密封类来约束支付方式的实现。

1. 定义密封支付接口

public sealed interface PaymentMethod permits CreditCard, PayPal, ApplePay {
    boolean process(double amount);
}

2. 实现具体支付方式

public final class CreditCard implements PaymentMethod {
    private final String cardNumber;
    private final String cardHolder;

    public CreditCard(String cardNumber, String cardHolder) {
        this.cardNumber = cardNumber;
        this.cardHolder = cardHolder;
    }

    @Override
    public boolean process(double amount) {
        System.out.printf("Processing credit card payment: %.2f%n", amount);
        // 简化演示,实际逻辑请实现安全加密与调用支付网关
        return true;
    }
}

public final class PayPal implements PaymentMethod {
    private final String email;

    public PayPal(String email) {
        this.email = email;
    }

    @Override
    public boolean process(double amount) {
        System.out.printf("Processing PayPal payment: %.2f%n", amount);
        return true;
    }
}

public final class ApplePay implements PaymentMethod {
    private final String deviceToken;

    public ApplePay(String deviceToken) {
        this.deviceToken = deviceToken;
    }

    @Override
    public boolean process(double amount) {
        System.out.printf("Processing Apple Pay payment: %.2f%n", amount);
        return true;
    }
}

注意:上述实现类使用 final 修饰,进一步保证它们不能被继承。若你想允许这些类继续被子类化,只需把 final 去掉即可。

3. 业务层面使用

public class PaymentService {
    public boolean execute(PaymentMethod method, double amount) {
        return method.process(amount);
    }
}

4. 测试

public class Demo {
    public static void main(String[] args) {
        PaymentService service = new PaymentService();

        PaymentMethod cc = new CreditCard("4111111111111111", "Alice");
        PaymentMethod pp = new PayPal("[email protected]");
        PaymentMethod ap = new ApplePay("device-token-123");

        service.execute(cc, 100.00);
        service.execute(pp, 200.00);
        service.execute(ap, 300.00);
    }
}

运行结果:

Processing credit card payment: 100.00
Processing PayPal payment: 200.00
Processing Apple Pay payment: 300.00

四、密封类的高级使用技巧

1. 结合 record 进行值对象封装

public sealed record PaymentDetails(double amount, String currency) permits CreditCard, PayPal, ApplePay {}

2. 使用 switch 表达式与 pattern matching

double total = switch (method) {
    case CreditCard cc -> cc.process(amount) ? amount : 0;
    case PayPal pp -> pp.process(amount) ? amount : 0;
    case ApplePay ap -> ap.process(amount) ? amount : 0;
};

3. 在测试中验证非法继承

// 下面这行代码会在编译期报错
class UnknownPayment implements PaymentMethod { /* ... */ }

这正是密封类的价值所在——在编译时就把非法的继承关系拒之门外。


五、总结

  • 密封类 为 Java 提供了一种在编译期约束继承层次的机制。
  • 通过 permits 关键字可以精确控制哪些类可以继承或实现。
  • 在实际项目中,密封类能帮助我们减少错误、提升代码安全性,并使层次结构更易理解。
  • recordswitch 等新特性配合使用,可进一步提升代码的表达力。

小提示:如果你在使用 IDE(如 IntelliJ IDEA、Eclipse)时发现 permits 关键字不被识别,请确认 JDK 版本至少为 17,并在项目的编译设置中将源码级别设置为 17 或更高。

希望这篇文章能帮助你快速上手 Java 17 的密封类,为你的项目带来更好的可维护性与安全性。祝编码愉快!

评论

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注