1 文章
2 sealed class
2.1 java的密封类的应用场景
密封类和密封接口(sealed classes and interfaces)在Java中是一项重要的语言特性,旨在通过限制哪些类或接口可以继承或实现特定的父类或接口,从而增强封装性和可维护性。以下是密封类的定义、使用方法以及一些应用场景。
定义密封类和密封接口
要定义一个密封类或接口,需要使用 sealed
修饰符,并在声明中指定允许扩展或实现它的其他类或接口(使用 permits
子句)。子类或子接口可以使用 final
、sealed
或 non-sealed
修饰符来进一步控制扩展或实现。
示例
// 定义一个密封接口 Shape,只允许 Circle, Rectangle, Triangle 实现
public sealed interface Shape permits Circle, Rectangle, Triangle {}
// 定义具体的实现类
public final class Circle implements Shape {
private final double radius;
public Circle(double radius) {
this.radius = radius;
}
public double radius() { return radius; }
}
public final class Rectangle implements Shape {
private final double area;
public Rectangle(double area) {
this.area = area;
}
public double area() { return area; }
}
public final class Triangle implements Shape {
private final double base;
public Triangle(double base) {
this.base = base;
}
public double base() { return base; }
}
在上面的示例中:
Shape
是一个密封接口,只允许Circle
、Rectangle
和Triangle
三个类实现。Circle
、Rectangle
和Triangle
使用final
修饰符,表示它们不能被进一步扩展。
密封类的应用场景
密封类和接口在以下几种场景中非常有用:
-
枚举用例的替代:
- 当你有一个特定的类型层次结构集合,并且希望确保该集合是受限的,例如状态机的状态,密封类可以类似于枚举,确保整个状态集是有限的且已知的。
-
密封接口的安全扩展:
- 在复杂的继承结构中,通过限制接口的实现,可以提高类型系统的安全性。例如,接口
PaymentMethod
可能只有几种具体实现,如CreditCard
、DebitCard
和PayPal
。
- 在复杂的继承结构中,通过限制接口的实现,可以提高类型系统的安全性。例如,接口
-
增强可读性和可维护性:
- 通过限制继承层次,可以提高代码可读性,使得代码的维护更加容易。这避免了由于未知子类导致的错误行为,并且可以在编译时进行更加严格的类型检查。
-
模式匹配优化:
- 密封类配合模式匹配可以大幅提高类型检查和匹配的效率。例如,处理有限的一组
Shape
实现时,可以使用switch
表达式直接匹配具体类型,而无需显式的实例检查。
- 密封类配合模式匹配可以大幅提高类型检查和匹配的效率。例如,处理有限的一组
-
API 设计:
- 在设计公共API时,密封类可以限制哪些类可以直接扩展API提供的类型。这有助于确保API的正确使用,并且在API演化过程中,能够更严格地控制兼容性。
举例:密封类与模式匹配
假设我们正在开发一个绘图应用,我们可以使用密封类和模式匹配来简化对不同 Shape
类处理的代码:
public static void processShape(Shape shape) {
switch (shape) {
case Circle c -> System.out.println("Processing a circle with radius: " + c.radius());
case Rectangle r -> System.out.println("Processing a rectangle with area: " + r.area());
case Triangle t -> System.out.println("Processing a triangle with base: " + t.base());
default -> System.out.println("Unknown shape.");
}
}
在这个例子中,由于 Shape
是密封的,编译器知道所有可能的子类,因此可以在编译时检查 switch
语句的完整性。
如果不用密封类,而使用传统的强制类型转换,代码如下:
public static void processShape(Shape shape) {
if (shape instanceof Circle) {
Circle c = (Circle) shape;
System.out.println("Processing a circle with radius: " + c.radius());
} else if (shape instanceof Rectangle) {
Rectangle r = (Rectangle) shape;
System.out.println("Processing a rectangle with area: " + r.area());
} else if (shape instanceof Triangle) {
Triangle t = (Triangle) shape;
System.out.println("Processing a triangle with base: " + t.base());
} else {
System.out.println("Unknown shape.");
}
}
对比与分析
- 类型检查:
- 使用
instanceof
进行类型检查,判断对象是否属于特定的子类。
- 使用
- 类型转换:
- 进行显式的类型转换,把父类引用转换为实际的子类引用。
- 未知类型处理:
- 因为没有密封类的限制,编译器无法确保所有可能的子类都在代码处理范围内,因此需要添加一个
else
分支处理未知类型。
- 因为没有密封类的限制,编译器无法确保所有可能的子类都在代码处理范围内,因此需要添加一个
缺点
- 冗长:类型检查和显式类型转换增加了样板代码,显得冗长。
- 不安全:编译器无法确保你已经涵盖了所有可能的子类,如果后来增加了新的子类,你需要手动更新这个检查逻辑,容易出错。
- 效率:每次进行类型检查和转换都会增加一定的性能开销,特别是在处理复杂类型层次结构时。
总结
密封类和密封接口通过限制继承和实现,提供了一种强有力的工具来增强代码的封装性、可读性和可维护性。这种特性在复杂的类型层次结构和公共API设计中尤为有用,使得代码设计更加受控和安全。结合模式匹配,密封类可以显著简化代码逻辑,提高代码的执行效率和安全性。
3 模式匹配
在Java中,模式匹配是指通过语法结构和语言特性来简化类型检查和类型转换,使代码更加清晰和简洁。模式匹配的引入旨在减少样板代码(boilerplate code),提高代码可读性,并降低出错的概率。
模式匹配的用途
模式匹配通常用于:
- 类型检查和转换:通过
instanceof
关键字进行类型检查后,同时绑定一个新的变量,该变量已经被类型转换。 - 相等性匹配:用于
switch
语句和表达式中,用来匹配不同的模式,从而简化代码。
模式匹配示例
1. instanceof
中的模式匹配
在模式匹配引入之前,我们需要显式地进行类型检查和类型转换。例如:
Object obj = "Hello, World!";
if (obj instanceof String) {
String s = (String) obj; // 显式类型转换
System.out.println("The length of the string is " + s.length());
}
有了模式匹配之后,instanceof
语句同时完成类型检查和变量绑定,代码变得更简洁:
Object obj = "Hello, World!";
if (obj instanceof String s) { // 模式匹配,直接绑定变量s
System.out.println("The length of the string is " + s.length());
} else {
System.out.println("The object is not a string");
}
2. switch
中的模式匹配
模式匹配在switch
语句中也非常有用,特别是当处理密封类(sealed classes)及其子类时。例如:
假设我们有一个密封类Shape
及其子类Circle
、Rectangle
和Triangle
:
sealed interface Shape permits Circle, Rectangle, Triangle {}
final class Circle implements Shape { double radius; }
final class Rectangle implements Shape { double area; }
final class Triangle implements Shape { double base; }
传统的switch
语句中,我们需要显式地进行类型检查和转换:
public static void printShape(Shape shape) {
if (shape instanceof Circle) {
Circle c = (Circle) shape;
System.out.println("The radius of the circle is " + c.radius);
} else if (shape instanceof Rectangle) {
Rectangle r = (Rectangle) shape;
System.out.println("The area of the rectangle is " + r.area);
} else if (shape instanceof Triangle) {
Triangle t = (Triangle) shape;
System.out.println("The base of the triangle is " + t.base);
} else {
System.out.println("Unknown shape");
}
}
使用模式匹配的switch
语句,代码变得更加简单:
public static void printShape(Shape shape) {
switch (shape) {
case Circle c -> System.out.println("The radius of the circle is " + c.radius);
case Rectangle r -> System.out.println("The area of the rectangle is " + r.area);
case Triangle t -> System.out.println("The base of the triangle is " + t.base);
default -> System.out.println("Unknown shape");
}
}
模式匹配的优势
- 简化代码:减少了显式类型转换和多余的类型检查代码,极大简化了代码。
- 提高可读性:通过减少样板代码,使程序员更容易理解代码逻辑。
- 降低出错率:减少手动类型转换的步骤,降低了类型转换错误的概率。
总结
模式匹配是Java中的一个强大特性,通过简化类型检查和类型转换操作、减少重复代码,提高了代码的可读性和安全性。它在Java 14中首次引入,作为预览功能,经过多次改进,现已在Java 17中成为正式特性。模式匹配使得开发者可以更高效地编写类型安全且易于维护的代码。