【JDK17 | 14】Java 17 深入剖析:密封类

颜淡慕潇 2024-10-11 13:35:02 阅读 55

引言

Java 17引入了一项重要的新特性——密封类(Sealed Classes),这标志着Java在面向对象编程领域的又一次重大进步。密封类提供了一种机制来精确控制类的继承链,使得类的设计者能够明确规定哪些类能够继承或实现该类。

一、密封类的作用

在面向对象编程中,继承是一种常见的行为,它允许我们复用代码并扩展现有功能。然而,有时候我们希望限制这种继承行为,以确保类的使用不会超出我们的预期。密封类正是为了解决这个问题而设计的,它限制了类的继承,使得类的继承更加可预测和安全。

二、已有的限制手段

在Java中,我们已经有几种方式来限制类的继承:

使用<code>final关键字修饰类,这样类就无法被继承。将类设置为非public(即package-private),这样类只能被同一个包下的类继承。

但这两种方式的粒度都比较粗,如果需要更精细的控制,就显得力不从心了。

三、新特性:密封类

Java 17中的密封类引入了几个重要的关键词:

sealed:修饰类或接口,表示该类或接口是密封的。non-sealed:修饰类或接口,表示该类或接口不是密封的。permits:用在extendsimplements之后,指定可以继承或实现的类。

四、实战应用

4.1定义密封类

假设我们要设计一个游戏,游戏中的英雄分为三大类:坦克、输出和辅助。每个种类下又有各种不同的具体英雄。我们可以使用密封类来定义这样的结构:

public sealed class Hero permits TankHero, AttackHero, SupportHero { -- -->

// 英雄基类

}

public non-sealed class TankHero extends Hero {

// 坦克英雄的抽象

}

public non-sealed class AttackHero extends Hero {

// 输出英雄的抽象

}

public non-sealed class SupportHero extends Hero {

// 辅助英雄的抽象

}

public final class Alistar extends TankHero {

// 坦克英雄:阿利斯塔

}

public final class Ezreal extends AttackHero {

// 输出英雄:伊泽瑞尔

}

public final class Soraka extends SupportHero {

// 辅助英雄:索拉卡

}

通过这种方式,我们确保了Hero类的继承是受控的,只有TankHeroAttackHeroSupportHero可以继承它,而具体的英雄实现则被标记为final,防止进一步的继承。

4.2密封类与模式匹配

密封类与Java的模式匹配(Pattern Matching)相结合,可以提供更强的类型安全和代码清晰度。例如,我们可以在switch语句中使用模式匹配来处理密封类的不同子类:

Shape rotate(Shape shape, double angle) {

return switch (shape) {

case Circle c -> c;

case Rectangle r -> shape.rotate(angle);

case Square s -> shape.rotate(angle);

};

}

这种模式匹配确保了所有可能的子类都被覆盖,如果缺少任何一个子类,编译器将会发出错误。

五、应用场景

5.1. 图形库设计

在设计图形库时,我们可能只想允许特定的几何形状类继承一个基类。例如,我们有一个Shape基类,我们只想允许CircleSquare继承它:

public sealed class Shape permits Circle, Square {

// Shape类的实现

}

public final class Circle extends Shape {

// Circle类的实现

}

public final class Square extends Shape {

// Square类的实现

}

在这个场景中,密封类确保了Shape类的继承是受控的,防止了意外的类扩展,提高了库的可靠性和一致性。

5.2. 游戏角色设计

在游戏开发中,我们可能需要定义不同类型的角色,例如坦克、输出和辅助角色,并且每个类别下有具体的实现。使用密封类可以确保角色类的继承结构清晰:

public sealed class Hero permits TankHero, AttackHero, SupportHero {

// 英雄基类的实现

}

public non-sealed class TankHero extends Hero {

// 坦克英雄的实现

}

public non-sealed class AttackHero extends Hero {

// 输出英雄的实现

}

public non-sealed class SupportHero extends Hero {

// 辅助英雄的实现

}

在这个例子中,Hero类被密封,并且只有TankHeroAttackHeroSupportHero可以继承它。这样的设计有助于维护游戏角色的一致性和可管理性。

5.3. 表达式解析

在构建表达式解析器时,我们可能需要定义一个表达式接口,并且只允许特定的表达式类实现它。例如,一个简单的数学表达式解析器可能只允许ConstantExprPlusExprTimesExpr实现Expr接口:

public sealed interface Expr permits ConstantExpr, PlusExpr, TimesExpr {

int eval();

}

public final class ConstantExpr implements Expr {

int value;

public ConstantExpr(int value) {

this.value = value;

}

public int eval() {

return value;

}

}

public final class PlusExpr implements Expr {

Expr left, right;

public PlusExpr(Expr left, Expr right) {

this.left = left;

this.right = right;

}

public int eval() {

return left.eval() + right.eval();

}

}

在这个场景中,密封接口限制了表达式的类型,使得表达式的处理更加安全和可预测。

六、实战指南

6.1定义密封类

要定义一个密封类,你需要使用sealed关键字,并在permits子句中指定允许的子类:

public sealed class MyClass permits ClassA, ClassB {

// 类的实现

}

6.2 定义密封接口

定义密封接口的方式与密封类类似,但是用于接口:

public sealed interface MyInterface permits ClassA, InterfaceB {

// 接口的定义

}

6.3 使用模式匹配

Java的模式匹配可以用来处理密封类的不同子类,提高代码的可读性和安全性:

public void handleShape(Shape shape) {

switch (shape) {

case Circle circle -> System.out.println("Circle with radius " + circle.radius);

case Square square -> System.out.println("Square with side " + square.side);

}

}

6.4 兼容性和反射

密封类与Java的反射API兼容,你可以使用isSealed()getPermittedSubclasses()方法来检查密封类的状态和获取允许的子类列表:

if (Shape.class.isSealed()) {

System.out.println("Shape is sealed.");

Class<?>[] subclasses = Shape.class.getPermittedSubclasses();

System.out.println("Permitted subclasses: " + Arrays.toString(subclasses));

}

七、总结

密封类为Java带来了一种新的控制继承的方式,它提高了代码的安全性和可维护性。通过限制类的继承,我们可以构建更加健壮和可预测的系统。随着Java语言的不断发展,密封类将成为面向对象编程中不可或缺的一部分。



声明

本文内容仅代表作者观点,或转载于其他网站,本站不以此文作为商业用途
如有涉及侵权,请联系本站进行删除
转载本站原创文章,请注明来源及作者。