# 问题

33. Sealed Class(密封类)如何限制子类继承?

# 标准答案

密封类(Sealed Class) 是 Java 15 引入的一项特性,它允许开发者限制一个类的继承范围,从而确保类的继承结构在设计时是可控的。这一特性使得开发者可以精确地控制哪些类可以继承自某个父类,而哪些类不能继承,从而提高代码的可维护性、可预测性和安全性。

# 1. 基本概念

  • 密封类通过在类声明中使用 sealed 修饰符来定义。它规定了只能从指定的子类中继承该类。
  • 子类继承密封类时,必须在类声明中使用 permits 关键字列出允许继承该类的子类。

例如:

public sealed class Shape permits Circle, Rectangle {
    // 密封类的内容
}

public final class Circle extends Shape {
    // Circle 类的内容
}

public final class Rectangle extends Shape {
    // Rectangle 类的内容
}

// 试图继承密封类时会报错
// public class Triangle extends Shape { }  // 编译错误
1
2
3
4
5
6
7
8
9
10
11
12
13
14

在上面的例子中,Shape 类被声明为密封类,并且只允许 CircleRectangle 类继承。如果你尝试定义其他的子类(例如 Triangle),编译器会报错。

# 2. 如何限制子类继承

密封类通过 permits 关键字指定哪些类可以继承。只列举了在 permits 中的类才能继承密封类,其他类无法继承该密封类。

例如:

public sealed class Shape permits Circle, Rectangle, Square {
    // Shape 的实现
}

public final class Circle extends Shape { /* 实现 */ }
public final class Rectangle extends Shape { /* 实现 */ }
public final class Square extends Shape { /* 实现 */ }

// 下面的类无法继承 Shape,编译时会报错
// public final class Triangle extends Shape { } // 编译错误
1
2
3
4
5
6
7
8
9
10

在这个例子中,只有 CircleRectangleSquare 类被允许继承 Shape 类,其他任何类都无法继承 Shape 类。

# 3. 密封类的继承层级

  • 密封类不仅限于直接继承它的子类,子类也可以是其他类的密封类,从而进一步限制继承层级。
  • 密封类可以是 final(表示没有其他子类可以继承),也可以是 non-sealed(表示该类的子类可以自由继承该类)。

例如:

public sealed class Shape permits Circle, Rectangle {
    // Shape 的实现
}

public non-sealed class Circle extends Shape {
    // Circle 类可以继续被继承
}

public final class Rectangle extends Shape {
    // Rectangle 不能被继承
}

public class Triangle extends Circle {
    // Triangle 继承自 Circle
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

在上面的例子中,Circlenon-sealed 类,意味着它可以被进一步继承,而 Rectanglefinal 类,不能被继承。

# 4. 使用密封类的好处

  • 控制继承结构:通过限制继承,密封类使得开发者能够更好地控制类的继承结构,避免无序的继承链。
  • 类型安全:密封类可以在代码中保证类型安全。例如,使用 switch 语句时,可以确保仅处理已知的子类。
  • 增强可维护性:密封类的继承结构更加明确和有序,使得代码更易于理解和维护。
  • 性能优化:Java 编译器可以对密封类的继承层次进行优化,提高程序的性能,尤其是在模式匹配(如 switch)中。

# 5. 密封类与接口的结合

密封类可以和接口一起使用。密封类不仅可以指定继承层次,还可以实现接口,从而进一步增强灵活性。

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

public final class Circle implements Shape {
    // 实现方法
    public double area() { return Math.PI * radius * radius; }
}

public final class Rectangle implements Shape {
    // 实现方法
    public double area() { return width * height; }
}
1
2
3
4
5
6
7
8
9
10
11
12
13

在这个例子中,Shape 是一个密封接口,只有 CircleRectangle 可以实现这个接口,其他类不能。

# 核心原理

密封类通过 sealed 修饰符和 permits 关键字来实现继承限制。在 Java 编译时,编译器会检查继承层次结构,确保只有允许的类才能继承密封类,而其他类会产生编译错误。

# 常见错误

  • 忘记列出允许继承的类:如果没有在 permits 中列出所有允许继承的类,编译时会报错。
  • 错误地继承密封类:如果尝试继承密封类,但没有在 permits 列表中指定允许继承的类,编译时会产生错误。

# 最佳实践

  • 使用密封类限制继承层次,确保代码中只有已知的类继承某个类。
  • 将密封类和 switch 语句结合使用,利用模式匹配来提高代码的可读性和安全性。

# 性能优化

密封类的继承结构经过编译时的优化,可以减少运行时的判断和类型转换,提高程序性能。特别是在模式匹配和 switch 语句中,密封类能够提供更加高效的执行路径。

# 深入追问

🔹 密封类如何与抽象类结合使用? 🔹 密封类能否与枚举结合使用? 🔹 密封类和传统类有什么主要区别?

# 相关面试题

  • 什么是 Java 中的密封类?
  • 密封类如何提升代码的可维护性和类型安全性?
  • 如何使用密封类在设计中控制类的继承层次?