JEP 409:密封类

官方原文(英文)地址: https://openjdk.java.net/jeps/409
个人原创翻译,转载请注明出处。

Summary

Enhance the Java programming language with sealed classes and interfaces. Sealed classes and interfaces restrict which other classes or interfaces may extend or implement them.

摘要

使用密封类和接口增强Java编程语言。密封类和接口可以限制哪些其他类或接口可以扩展或实现它们。

History

Sealed Classes were proposed by JEP 360 and delivered in JDK 15 as a preview feature. They were proposed again, with refinements, by JEP 397 and delivered in JDK 16 as a preview feature. This JEP proposes to finalize Sealed Classes in JDK 17, with no changes from JDK 16.

历史

密封类由JEP 360提出并在JDK 15中作为预览特性。它们在JEP 397中再次提出,并带有优化,在JDK 16中作为预览特性交付。本JEP提出在JDK 17中完成密封类,并与JDK 16相比无变化。

Goals

  • Allow the author of a class or interface to control which code is responsible for implementing it.
  • Provide a more declarative way than access modifiers to restrict the use of a superclass.
  • Support future directions in pattern matching by providing a foundation for the exhaustive analysis of patterns.

目标

  • 允许类或接口的作者可以控制哪些代码有责任实现它。
  • 提供比访问修饰符更具声明性的方式来限制父类的使用。
  • 通过为模式的详尽分析提供一个标准,以支持模式匹配的未来方向。

Non-Goals

  • It is not a goal to provide new forms of access control such as "friends".
  • It is not a goal to change final in any way.

非目标

  • 提供诸如“friends”等新形式的访问控制不是目标。
  • 以任何方式改变final不是目标。

Motivation

The object-oriented data model of inheritance hierarchies of classes and interfaces has proven to be highly effective in modeling the real-world data processed by modern applications. This expressiveness is an important aspect of the Java language.

动机

类和接口的继承层次结构的面向对象数据模型已被证明在对现代应用处理的现实世界数据进行建模方面非常有效。这种表现力是Java语言的一个重要方面。

There are, however, cases where such expressiveness can usefully be tamed. For example, Java supports enum classes to model the situation where a given class has only a fixed number of instances. In the following code, an enum class lists a fixed set of planets. They are the only values of the class, therefore you can switch over them exhaustively — without having to write a default clause:

然而,在某些情况下,可以有效地控制这种表现力。例如,Java支持枚举类来模拟给定类只有固定数量实例的情况。在以下代码中,枚举类列出了一组固定的行星。它们是该类仅有的取值,因此您可以彻底switch处理它们——无需编写default子句:

enum Planet { MERCURY, VENUS, EARTH }

Planet p = ...
switch (p) {
  case MERCURY: ...
  case VENUS: ...
  case EARTH: ...
}
enum Planet { MERCURY, VENUS, EARTH }

Planet p = ...
switch (p) {
  case MERCURY: ...
  case VENUS: ...
  case EARTH: ...
}

Using enum classes to model fixed sets of values is often helpful, but sometimes we want to model a fixed set of kinds of values. We can do this by using a class hierarchy not as a mechanism for code inheritance and reuse but, rather, as a way to list kinds of values. Building on our planetary example, we might might model the kinds of values in the astronomical domain as follows:

使用枚举类对固定的值集进行建模通常很有帮助,但有时我们想要对一类固定的值进行建模。我们可以通过使用类层次结构来做到这一点,而不是作为代码继承和重用的机制,而是作为列出各种值的一种方式。以我们的行星示例为基础,我们可以对天文领域中的各种值进行建模,如下所示:

interface Celestial { ... }
final class Planet implements Celestial { ... }
final class Star   implements Celestial { ... }
final class Comet  implements Celestial { ... }
interface Celestial { ... }
final class Planet implements Celestial { ... }
final class Star   implements Celestial { ... }
final class Comet  implements Celestial { ... }

This hierarchy does not, however, reflect the important domain knowledge that there are only three kinds of celestial objects in our model. In these situations, restricting the set of subclasses or subinterfaces can streamline the modeling.

然而,这种层次结构并没有反映重要的领域信息,即我们的模型中只有三种天体。在这些情况下,限制子类或子接口的集合可以简化建模。

Consider another example: In a graphics library, the author of a class Shape may intend that only particular classes can extend Shape, since much of the library's work involves handling each kind of shape in the appropriate way. The author is interested in the clarity of code that handles known subclasses of Shape, and not interested in writing code to defend against unknown subclasses of Shape. Allowing arbitrary classes to extend Shape, and thus inherit its code for reuse, is not a goal in this case. Unfortunately, Java assumes that code reuse is always a goal: If Shape can be extended at all, then it can be extended by any number of classes. It would be helpful to relax this assumption so that an author can declare a class hierarchy that is not open for extension by arbitrary classes. Code reuse would still be possible within such a closed class hierarchy, but not beyond.

考虑另一个例子:在图形库中,类Shape的作者可能打算只允许特定的类可以继承Shape,因为该库的大部分工作涉及以适当的方式处理每种形状。作者感兴趣的是处理Shape已知子类的代码的清晰度,而不是编写代码来防御未知的Shape子类。允许任意类扩展Shape,从而继承其代码以供重用,在这种情况下不是目标。不幸的是,Java假定代码重用始终是一个目标:如果Shape可以扩展,那么它可以由任意数量的类扩展。放宽这个假设是有帮助的,这样作者就可以声明一个类层次结构,该层次结构不能被任意类扩展。在这样一个封闭的类层次结构中,代码重用仍然是可能的,但不能超越一切。

Java developers are familiar with the idea of restricting the set of subclasses because it often crops up in API design. The language provides limited tools in this area: either make a class final, so it has zero subclasses, or make a class or its constructor package-private, so it can only have subclasses in the same package. An example of a package-private superclass appears in the JDK:

Java开发者很熟悉限制子类范围的想法,因为它经常出现在API设计中。该语言在这方面提供了有限的工具:要么将类设为final,使其具有零个子类,要么将类或其构造器设为包级私有,因此它只能在同一个包中包含子类。包级私有超类的示例出现在JDK中

package java.lang;

abstract class AbstractStringBuilder {...}
public final class StringBuffer  extends AbstractStringBuilder {...}
public final class StringBuilder extends AbstractStringBuilder {...}
package java.lang;

abstract class AbstractStringBuilder {...}
public final class StringBuffer  extends AbstractStringBuilder {...}
public final class StringBuilder extends AbstractStringBuilder {...}

The package-private approach is useful when the goal is code reuse, such as having the subclasses of AbstractStringBuilder share its code for append. However, the approach is useless when the goal is modeling alternatives, since user code cannot access the key abstraction — the superclass — in order to switch over it. Allowing users to access the superclass without also allowing them to extend it cannot be easily specified without resorting to brittle tricks involving non-public constructors — which do not work for interfaces. In a graphics library that declares Shape and its subclasses, it would be unfortunate if only one package could access Shape.

当目标是代码重用时,包级私有方法很有用,例如AbstractStringBuilder的子类共享它的append代码。然而,当目标是对备选进行建模时,包级私有的处理就没用了,因为用户代码无法访问关键抽象——父类——以便对其进行switch处理。允许用户访问父类而不允许他们继承它,这是不容易指定的,除非求助于涉及非公有构造器的脆弱技巧——而这些技巧不适用于接口。在声明Shape及其子类的图形库中,如果只有一个包可以访问Shape,那将是不幸的。

In summary, it should be possible for a superclass to be widely accessible (since it represents an important abstraction for users) but not widely extensible (since its subclasses should be restricted to those known to the author). Such a superclass should be able to express that it is co-developed with a given set of subclasses, both to document intent for the reader and to allow enforcement by the Java compiler. At the same time, the superclass should not unduly constrain its subclasses by, e.g., forcing them to be final or preventing them from defining their own state.

总之,父类应该可以被广泛访问(因为它代表用户的重要抽象)但不能广泛扩展(因为它的子类应该仅限于作者已知的那些)。这样的父类应该能够表示它是与一组给定的子类共同开发的,既可以记录读者的意图,又可以让Java编译器强制执行。同时,父类不应过度限制其子类,例如强制它们为final或阻止它们定义自己的状态。

Description

A sealed class or interface can be extended or implemented only by those classes and interfaces permitted to do so.

描述

密封类或接口只能由允许的类和接口进行继承或实现。

A class is sealed by applying the sealed modifier to its declaration. Then, after any extends and implements clauses, the permits clause specifies the classes that are permitted to extend the sealed class. For example, the following declaration of Shape specifies three permitted subclasses:

一个类通过对它的声明应用sealed修饰符来密封。然后,在任何extendsimplements子句之后,用permit子句指定允许扩展密封类的类。例如,以下Shape声明指定了三个允许的子类:

package com.example.geometry;

public abstract sealed class Shape
permits Circle, Rectangle, Square {...}
package com.example.geometry;

public abstract sealed class Shape
permits Circle, Rectangle, Square {...}

The classes specified by permits must be located near the superclass: either in the same module (if the superclass is in a named module) or in the same package (if the superclass is in the unnamed module). For example, in the following declaration of Shape, its permitted subclasses are all located in different packages of the same named module:

permit指定的类必须位于父类附近:在同一个模块中(如果父类在已命名的模块中),或在同一个包中(如果父类在未命名的模块中)。例如,在下面的Shape声明中,其允许的子类都位于同名模块的不同包中:

package com.example.geometry;

public abstract sealed class Shape 
permits com.example.polar.Circle,
        com.example.quad.Rectangle,
        com.example.quad.simple.Square {...}
package com.example.geometry;

public abstract sealed class Shape 
permits com.example.polar.Circle,
        com.example.quad.Rectangle,
        com.example.quad.simple.Square {...}

When the permitted subclasses are small in size and number, it may be convenient to declare them in the same source file as the sealed class. When they are declared in this way, the sealed class may omit the permits clause, and the Java compiler will infer the permitted subclasses from the declarations in the source file (which may be auxiliary or nested classes). For example, if the following code is found in Root.java, then the sealed class Root is inferred to have three permitted subclasses:

当允许的子类的大小和数量都较小时,将它们声明在与密封类相同的源文件中可能会很方便。当它们以这种方式声明时,密封类可能会省略permit子句,Java编译器将从源文件中的声明(可能是辅助类或嵌套类)推断出允许的子类。例如,如果在Root.java中找到以下代码,则推断密封类Root具有三个允许的子类:

abstract sealed class Root { ... 
    final class A extends Root { ... }
    final class B extends Root { ... }
    final class C extends Root { ... }
}
abstract sealed class Root { ... 
    final class A extends Root { ... }
    final class B extends Root { ... }
    final class C extends Root { ... }
}

Classes specified by permits must have a canonical name, otherwise a compile-time error is reported. This means that anonymous classes and local classes cannot be permitted subtypes of a sealed class.

permits指定的类必须具有规范名称,否则会报告编译期错误。这意味着匿名类和本地类不能成为密封类的子类型。

A sealed class imposes three constraints on its permitted subclasses:

  1. The sealed class and its permitted subclasses must belong to the same module, and, if declared in an unnamed module, to the same package.
  2. Every permitted subclass must directly extend the sealed class.
  3. Every permitted subclass must use a modifier to describe how it propagates the sealing initiated by its superclass:
    • A permitted subclass may be declared final to prevent its part of the class hierarchy from being extended further. (Record classes are implicitly declared final.)
    • A permitted subclass may be declared sealed to allow its part of the hierarchy to be extended further than envisaged by its sealed superclass, but in a restricted fashion.
    • A permitted subclass may be declared non-sealed so that its part of the hierarchy reverts to being open for extension by unknown subclasses. A sealed class cannot prevent its permitted subclasses from doing this. (The modifier non-sealed is the first hyphenated keyword proposed for Java.)

密封类对其允许的子类施加三个约束:

  1. 密封类及其允许的子类必须属于同一个模块,并且,如果在未命名的模块中声明,则属于同一个包。
  2. 每个允许的子类必须直接扩展密封类。
  3. 每个允许的子类必须使用修饰符来描述它如何传播由其父类发起的密封:
    • 允许的子类可以声明为final,以防止其在类层次结构中的部分被进一步向下继承。(记录类是隐式final的。)
    • 允许的子类可以声明为sealed,以允许其层次结构的一部分比其密封的父类所设想的扩展得更远,但以受限制的方式。
    • 一个允许的子类可以被声明为non-sealed,这样它的层次结构部分就会恢复到对未知子类的扩展开放。密封类不能阻止其允许的子类这样做。(non-sealed是Java中第一个带连字符的关键字。)

As an example of the third constraint, Circle and Square may be final while Rectangle is sealed and we add a new subclass, WeirdShape, that is non-sealed:

作为第三条约束的例子,CircleSquare可以为final的,Rectanglesealed的,而我们添加一个新的子类WeirdShape,是non-sealed的:

package com.example.geometry;

public abstract sealed class Shape
    permits Circle, Rectangle, Square, WeirdShape {...}

public final class Circle extends Shape {...}

public sealed class Rectangle extends Shape 
    permits TransparentRectangle, FilledRectangle {...}
public final class TransparentRectangle extends Rectangle {...}
public final class FilledRectangle extends Rectangle {...}

public final class Square extends Shape { ... }

public non-sealed class WeirdShape extends Shape { ... }
package com.example.geometry;

public abstract sealed class Shape
    permits Circle, Rectangle, Square, WeirdShape {...}

public final class Circle extends Shape {...}

public sealed class Rectangle extends Shape 
    permits TransparentRectangle, FilledRectangle {...}
public final class TransparentRectangle extends Rectangle {...}
public final class FilledRectangle extends Rectangle {...}

public final class Square extends Shape { ... }

public non-sealed class WeirdShape extends Shape { ... }

Even though the WeirdShape is open to extension by unknown classes, all instances of those subclasses are also instances of WeirdShape. Therefore code written to test whether an instance of Shape is either a Circle, a Rectangle, a Square, or a WeirdShape remains exhaustive.

即使WeirdShape对未知类的扩展开放,这些子类的所有实例也是WeirdShape的实例。因此,编写用于测试Shape实例是CircleRectangleSquare还是WeirdShape的代码仍然是详尽的。

Exactly one of the modifiers final, sealed, and non-sealed must be used by each permitted subclass. It is not possible for a class to be both sealed (implying subclasses) and final (implying no subclasses), or both non-sealed (implying subclasses) and final (implying no subclasses), or both sealed (implying restricted subclasses) and non-sealed (implying unrestricted subclasses).

每个允许的子类必须使用一个且仅一个修饰符finalsealednon-sealed。一个类不可能既是sealed的(暗示有子类)又是final的(暗示没有子类),或者既是non-sealed的(暗示子类)又是final的(暗示没有子类),或者既是sealed的(暗示受限制的子类)又是non-sealed的(暗示不受限制的子类)。

(The final modifier can be considered a strong form of sealing, where extension/implementation is prohibited completely. That is, final is conceptually equivalent to sealed + a permits clause which specifies nothing, though such a permits clause cannot be written.)

final修饰符可以被认为是一种强密封形式,其中完全禁止扩展/实现。也就是说,final在概念上等同于sealed加上一个不指定任何内容的permits子句;尽管这样的permits子句不能编写。)

A class which is sealed or non-sealed may be abstract, and have abstract members. A sealed class may permit subclasses which are abstract, providing they are then sealed or non-sealed, rather than final.

sealednon-sealed的类可以是abstract的,并且具有abstract的成员。sealed类可以允许abstract的子类,前提是它们是sealed的或non-sealed的,而不是final的。

It is a compile-time error if any class extends a sealed class but is not permitted to do so.

如果任何类扩展了sealed类但不允许这样做,则会导致编译时错误。

Class accessibility

Because extends and permits clauses make use of class names, a permitted subclass and its sealed superclass must be accessible to each other. However, permitted subclasses need not have the same accessibility as each other, or as the sealed class. In particular, a subclass may be less accessible than the sealed class. This means that, in a future release when pattern matching is supported by switches, some code will not be able to exhaustively switch over the subclasses unless a default clause (or other total pattern) is used. Java compilers will be encouraged to detect when switch is not as exhaustive as its original author imagined it would be, and customize the error message to recommend a default clause.

类可访问性

由于extendspermits子句使用类名,因此允许的子类及其密封超类必须可以相互访问。但是,允许的子类彼此之间不需要具有相同的可访问性,也不需要与密封类具有相同的可访问性。特别是,子类可能比密封类更难访问;这意味着,在未来版本中,当switch支持模式匹配时,除非使用default子句(或其他总模式),否则某些代码将无法彻底switch子类。将鼓励Java编译器检测switch何时不像它的原作者想象的那样详尽,并自定义错误消息以推荐default子句。

Sealed interfaces

As for classes, an interface can be sealed by applying the sealed modifier to the interface. After any extends clause to specify superinterfaces, the implementing classes and subinterfaces are specified with a permits clause. For example, the planetary example from the introduction can be rewritten as follows:

密封接口

对于类,可以通过对接口应用sealed修饰符来密封接口。在任何用于指定父接口的extends子句之后,使用permits子句指定实现类和子接口。例如,介绍中的行星示例可以改写如下:

sealed interface Celestial 
    permits Planet, Star, Comet { ... }

final class Planet implements Celestial { ... }
final class Star   implements Celestial { ... }
final class Comet  implements Celestial { ... }
sealed interface Celestial 
    permits Planet, Star, Comet { ... }

final class Planet implements Celestial { ... }
final class Star   implements Celestial { ... }
final class Comet  implements Celestial { ... }

Here is another classic example of a class hierarchy where there is a known set of subclasses: modeling mathematical expressions.

这是类层次结构的另一个经典示例,其中有一组已知的子类:数学表达式建模。

package com.example.expression;

public sealed interface Expr
    permits ConstantExpr, PlusExpr, TimesExpr, NegExpr { ... }

public final class ConstantExpr implements Expr { ... }
public final class PlusExpr     implements Expr { ... }
public final class TimesExpr    implements Expr { ... }
public final class NegExpr      implements Expr { ... }
package com.example.expression;

public sealed interface Expr
    permits ConstantExpr, PlusExpr, TimesExpr, NegExpr { ... }

public final class ConstantExpr implements Expr { ... }
public final class PlusExpr     implements Expr { ... }
public final class TimesExpr    implements Expr { ... }
public final class NegExpr      implements Expr { ... }

Sealing and record classes

Sealed classes work well with record classes. Record classes are implicitly final, so a sealed hierarchy of record classes is slightly more concise than the example above:

密封与记录类

密封类与记录类配合得很好。记录类是隐式final的,所以一个记录类的密封层次比上面的示例稍微简洁一些:

package com.example.expression;

public sealed interface Expr
    permits ConstantExpr, PlusExpr, TimesExpr, NegExpr {...}

public record ConstantExpr(int i)       implements Expr {...}
public record PlusExpr(Expr a, Expr b)  implements Expr {...}
public record TimesExpr(Expr a, Expr b) implements Expr {...}
public record NegExpr(Expr e)           implements Expr {...}
package com.example.expression;

public sealed interface Expr
    permits ConstantExpr, PlusExpr, TimesExpr, NegExpr {...}

public record ConstantExpr(int i)       implements Expr {...}
public record PlusExpr(Expr a, Expr b)  implements Expr {...}
public record TimesExpr(Expr a, Expr b) implements Expr {...}
public record NegExpr(Expr e)           implements Expr {...}

The combination of sealed classes and records is sometimes referred to as algebraic data types: Records allow us to express product types, and sealed classes allow us to express sum types.

密封类和记录的组合有时被称为代数数据类型:记录允许我们表达product类型,密封类允许我们表达sum类型

Sealed classes and conversions

A cast expression converts a value to a type. A type instanceof expression tests a value against a type. Java is extremely permissive about the types that are allowed in these kinds of expressions. For example:

密封类与转换

强制转换表达式将值转换为类型。类型instanceof表达式根据类型测试值。Java对此类表达式中允许的类型极为宽容。例如:

interface I {}
class C {} // does not implement I

void test (C c) {
    if (c instanceof I) 
        System.out.println("It's an I");
}
interface I {}
class C {} // 没有实现I

void test (C c) {
    if (c instanceof I) 
        System.out.println("It's an I");
}

This program is legal even though it is currently not possible for a C object to implement the interface I. Of course, as the program evolves, it might be:

这个程序是合法的,尽管目前C对象不可能实现接口I。当然,随着程序的发展,它可能是:

...
class B extends C implements I {}

test(new B()); 
// Prints "It's an I"
...
class B extends C implements I {}

test(new B()); 
// 打印 "It's an I"

The type conversion rules capture a notion of open extensibility. The Java type system does not assume a closed world. Classes and interfaces can be extended at some future time, and casting conversions compile to runtime tests, so we can safely be flexible.

类型转换规则体现了开放扩展的概念。Java类型系统不假设一个封闭的世界。类和接口可以在未来的某个时间扩展,并且强制转换编译为运行时测试,因此我们可以安全地做到灵活。

However, at the other end of the spectrum the conversion rules do address the case where a class can definitely not be extended, i.e., when it is a final class.

然而,在另一个方面,转换规则确实解决了类绝对不能扩展的情况,即当它是final类时。

interface I {}
final class C {}

void test (C c) {
    if (c instanceof I) 
        System.out.println("It's an I");
}
interface I {}
final class C {}

void test (C c) {
    if (c instanceof I) 
        System.out.println("It's an I");
}

The method test fails to compile, since the compiler knows that there can be no subclass of C, so since C does not implement I then it is never possible for a C value to implement I. This is a compile-time error.

方法test无法编译,因为编译器知道不能有C的子类,所以既然C没有实现I,那么C的值永远不可能实现I。这是一个编译期错误。

What if C is not final, but sealed? Its direct subclasses are explicitly enumerated, and — by the definition of being sealed — in the same module, so we expect the compiler to look to see if it can spot a similar compile-time error. Consider the following code:

如果C不是final的,而是sealed的怎么办?它的直接子类被显式枚举,并且——根据被密封的定义——在同一个模块中,所以我们希望编译器查看它是否可以发现类似的编译时错误。考虑以下代码:

interface I {}
sealed class C permits D {}
final class D extends C {}

void test (C c) {
    if (c instanceof I) 
        System.out.println("It's an I");
}
interface I {}
sealed class C permits D {}
final class D extends C {}

void test (C c) {
    if (c instanceof I) 
        System.out.println("It's an I");
}

Class C does not implement I, and is not final, so by the existing rules we might conclude that a conversion is possible. C is sealed, however, and there is one permitted direct subclass of C, namely D. By the definition of sealed types, D must be either final, sealed, or non-sealed. In this example, all the direct subclasses of C are final and do not implement I. This program should therefore be rejected, since there cannot be a subtype of C that implements I.

C类不实现I,也不是final的,因此根据现有规则,我们可能会得出转换是可能的结论。但是,Csealed的,并且C有一个允许的直接子类,即D。根据密封类型的定义,D必须是final的、sealed的或non-sealed的。在这个例子中,C的所有直接子类都是final并且不实现I。因此这个程序应该被拒绝,因为不可能有C的子类型实现I

In contrast, consider a similar program where one of the direct subclasses of the sealed class is non-sealed:

相反,考虑一个类似的程序,其中密封类的直接子类之一是non-sealed的:

interface I {}
sealed class C permits D, E {}
non-sealed class D extends C {}
final class E extends C {}

void test (C c) {
    if (c instanceof I) 
        System.out.println("It's an I");
}
interface I {}
sealed class C permits D, E {}
non-sealed class D extends C {}
final class E extends C {}

void test (C c) {
    if (c instanceof I) 
        System.out.println("It's an I");
}

This is type-correct, since it is possible for a subtype of the non-sealed type D to implement I.

这是类型正确的,因为non-sealed类型D的子类型可以实现I

Consequently, supporting sealed classes leads to a change in the definition of narrowing reference conversion to navigate sealed hierarchies to determine at compile time which conversions are not possible.

因此,支持sealed类会导致缩小引用转换的定义发生变化,以导航密封层次结构,从而在编译时确定哪些转换是不可能的。

Sealed classes in the JDK

An example of how sealed classes might be used in the JDK is in the java.lang.constant package that models descriptors for JVM entities:

JDK中的密封类

在JDK中如何使用密封类的一个例子是在java.lang.constant包中,它为JVM实体的描述符进行建模:

package java.lang.constant;

public sealed interface ConstantDesc
    permits String, Integer, Float, Long, Double,
            ClassDesc, MethodTypeDesc, DynamicConstantDesc {...}

// ClassDesc is designed for subclassing by JDK classes only
public sealed interface ClassDesc extends ConstantDesc
    permits PrimitiveClassDescImpl, ReferenceClassDescImpl {...}
final class PrimitiveClassDescImpl implements ClassDesc {...}
final class ReferenceClassDescImpl implements ClassDesc {...} 

// MethodTypeDesc is designed for subclassing by JDK classes only
public sealed interface MethodTypeDesc extends ConstantDesc
    permits MethodTypeDescImpl {...}
final class MethodTypeDescImpl implements MethodTypeDesc {...}

// DynamicConstantDesc is designed for subclassing by user code
public non-sealed abstract class DynamicConstantDesc implements ConstantDesc {...}
package java.lang.constant;

public sealed interface ConstantDesc
    permits String, Integer, Float, Long, Double,
            ClassDesc, MethodTypeDesc, DynamicConstantDesc {...}

// ClassDesc设计用于仅通过JDK的类进行子类化
public sealed interface ClassDesc extends ConstantDesc
    permits PrimitiveClassDescImpl, ReferenceClassDescImpl {...}
final class PrimitiveClassDescImpl implements ClassDesc {...}
final class ReferenceClassDescImpl implements ClassDesc {...} 

// MethodTypeDesc设计用于仅通过JDK的类进行子类化
public sealed interface MethodTypeDesc extends ConstantDesc
    permits MethodTypeDescImpl {...}
final class MethodTypeDescImpl implements MethodTypeDesc {...}

// DynamicConstantDesc设计用于通过用户代码进行子类化
public non-sealed abstract class DynamicConstantDesc implements ConstantDesc {...}

Sealed classes and pattern matching

A significant benefit of sealed classes will be realized in JEP 406, which proposes to extend switch with pattern matching. Instead of inspecting an instance of a sealed class with if-else chains, user code will be able to use a switch enhanced with patterns. The use of sealed classes will allow the Java compiler to check that the patterns are exhaustive.

密封类与模式匹配

JEP 406将实现密封类的显着优势,它建议使用模式匹配扩展switch。用户代码将能够使用通过模式增强的switch,而不是检查带有if-else链的密封类的实例。密封类的使用将允许Java编译器检查模式是否详尽。

For example, consider this code using the sealed hierarchy declared earlier:

例如,考虑使用先前声明的sealed层次结构的此代码:

Shape rotate(Shape shape, double angle) {
        if (shape instanceof Circle) return shape;
        else if (shape instanceof Rectangle) return shape;
        else if (shape instanceof Square) return shape;
        else throw new IncompatibleClassChangeError();
}
Shape rotate(Shape shape, double angle) {
        if (shape instanceof Circle) return shape;
        else if (shape instanceof Rectangle) return shape;
        else if (shape instanceof Square) return shape;
        else throw new IncompatibleClassChangeError();
}

The Java compiler cannot ensure that the instanceof tests cover all the permitted subclasses of Shape. The final else clause is actually unreachable, but this cannot be verified by the compiler. More importantly, no compile-time error message would be issued if the instanceof Rectangle test was omitted.

Java编译器无法确保instanceof测试涵盖所有允许的Shape子类。最后的else子句实际上是不可达的,但这无法被编译器验证。更重要的是,如果省略instanceof Rectangle测试,则不会发出编译时错误消息。

In contrast, with pattern matching for switch (JEP 406)the compiler can confirm that every permitted subclass of Shape is covered, so no default clause or other total pattern is needed. The compiler will, moreover, issue an error message if any of the three cases is missing:

相比之下,使用switch的模式匹配 (JEP 406),编译器可以确认Shape的每个允许的子类都被覆盖,因此不需要default子句或其他总模式。此外,如果缺少以下三种情况中的任何一种,编译器将发出错误消息:

Shape rotate(Shape shape, double angle) {
    return switch (shape) {   // pattern matching switch
        case Circle c    -> c; 
        case Rectangle r -> r.rotate(angle);
        case Square s    -> s.rotate(angle);
        // no default needed!
    }
}
Shape rotate(Shape shape, double angle) {
    return switch (shape) {   // switch的模式匹配
        case Circle c    -> c; 
        case Rectangle r -> r.rotate(angle);
        case Square s    -> s.rotate(angle);
        // 不需要default!
    }
}

Java Grammar

The grammar for class declarations is amended to the following:

NormalClassDeclaration:
  {ClassModifier} class TypeIdentifier [TypeParameters]
    [Superclass] [Superinterfaces] [PermittedSubclasses] ClassBody

ClassModifier:
  (one of)
  Annotation public protected private
  abstract static sealed final non-sealed strictfp

PermittedSubclasses:
  permits ClassTypeList

ClassTypeList:
  ClassType {, ClassType}

Java语法

类声明的语法修改如下:

普通类声明:
  {类修饰符} class 类型标识符 [类型参数]
    [父类] [父接口] [允许的子类] 类体

类修饰符:
  (其中之一)
  Annotation public protected private
  abstract static sealed final non-sealed strictfp

允许的子类:
  permits 类类型列表

类类型列表:
  类类型 {, 类类型}

JVM support for sealed classes

The Java Virtual Machine recognizes sealed classes and interfaces at runtime, and prevents extension by unauthorized subclasses and subinterfaces.

JVM对密封类的支持

Java虚拟机在运行时识别sealed类和接口,并防止未经授权的子类和子接口进行扩展。

Although sealed is a class modifier, there is no ACC_SEALED flag in the ClassFile structure. Instead, the class file of a sealed class has a PermittedSubclasses attribute which implicitly indicates the sealed modifier and explicitly specifies the permitted subclasses:

尽管sealed是一个类修饰符,但ClassFile结构中没有ACC_SEALED标志。相反,密封类的class文件具有PermittedSubclasses属性,该属性隐式指示sealed修饰符并显式指定允许的子类:

PermittedSubclasses_attribute {
    u2 attribute_name_index;
    u4 attribute_length;
    u2 number_of_classes;
    u2 classes[number_of_classes];
}
PermittedSubclasses_attribute {
    u2 attribute_name_index;
    u4 attribute_length;
    u2 number_of_classes;
    u2 classes[number_of_classes];
}

The list of permitted subclasses is mandatory. Even when the permitted subclasses are inferred by the compiler, those inferred subclasses are explicitly included in the PermittedSubclasses attribute.

允许的子类列表是强制性的。即使编译器推断出允许的子类,这些推断的子类也明确包含在PermittedSubclasses属性中。

The class file of a permitted subclass carries no new attributes.

允许的子类的class文件没有新属性。

When the JVM attempts to defines a class whose superclass or superinterface has a PermittedSubclasses attribute, the class being defined must be named by the attribute. Otherwise, an IncompatibleClassChangeError is thrown.

当JVM尝试定义其父类或父接口具有PermittedSubclasses属性的类时,所定义的类必须由该属性命名。否则,将引发IncompatibleClassChangeError

Reflection API

The following public methods will be added to java.lang.Class:

  • Class<?>[] getPermittedSubclasses()
  • boolean isSealed()

反射API

下面的public方法将被添加到java.lang.Class中:

  • Class<?>[] getPermittedSubclasses()
  • boolean isSealed()

The method getPermittedSubclasses() returns an array containing java.lang.Class objects representing all the permitted subclasses of the class, if it is sealed. It returns an empty array if the class is not sealed.

方法getPermittedSubclasses()返回一个包含java.lang.Class对象的数组,如果该类是密封的,则表示该类的所有允许的子类;如果该类未密封,则返回一个空数组。

The method isSealed returns true if the given class or interface is sealed. (Compare with isEnum.)

如果给定的类或接口是密封的,则方法isSealed返回true。(就像isEnum。)

Future Work

A common pattern, especially when writing APIs, is to define a public type as an interface and implement it with a single private class. With sealed classes this can be expressed more precisely, as a sealed public interface with a single permitted private implementation. Thus the type is widely accessible but the implementation is not, and cannot be extended in any way.

未来的工作

一种常见的模式,尤其是在编写API时,是将公共类型定义为接口并使用单个私有类实现它。使用密封类,这可以更准确地表达为具有单个允许的私有实现的密封公共接口。因此该类型可以广泛访问,但实现不是,并且不能以任何方式扩展。

public sealed interface Foo permits MyFooImpl { } 
private final class MyFooImpl implements Foo { }
public sealed interface Foo permits MyFooImpl { } 
private final class MyFooImpl implements Foo { }

A clumsiness of this approach is that implementation methods that accept Foo objects require explicit casts, for example:

这种方法的一个笨拙之处在于,接受Foo对象的实现方法需要显式转换,例如:

void m(Foo f) { 
    MyFooImpl mfi = (MyFooImpl) f;
    ...
}
void m(Foo f) { 
    MyFooImpl mfi = (MyFooImpl) f;
    ...
}

The cast here seems unnecessary, since we know it should always succeed. Yet there is an implicit semantic assumption in the cast, which is that the class MyFooImpl is the only implementation of Foo. There is no way for the author to capture this intuition so that it can be checked at compile time. Should, in time, Foo permit an additional implementation, this cast would remain type-correct but may fail at runtime. In other words, the semantic assumption would be broken but the compiler cannot alert the developer of that fact.

这里的转换似乎没有必要,因为我们知道它应该总是成功的。然而,转换中有一个隐含的语义假设,即MyFooImpl类是Foo的唯一实现。作者没有办法捕捉到这种直觉,以便在编译时进行检查。如果Foo允许额外的实现,这个转换将保持类型正确,但可能会在运行时失败。换句话说,语义假设将被打破,但编译器无法提醒开发人员这一事实。

With the precision of sealed hierarchies it may be worth providing developers with the means to express such semantic assumptions, and for the compiler to check them. This could be achieved by adding a new form of reference conversion for assignment contexts which allows the conversion of a sealed supertype to its only subtype, for example:

凭借密封层次结构的精确性,可能值得为开发人员提供表达此类语义假设的方法,并让编译器对其进行检查。这可以通过为赋值上下文添加一种新的引用转换形式来实现,它允许将密封的超类型转换为其唯一的子类型,例如:

MyFooImpl mfi = f; // Allowed because the compiler sees that MyFooImpl
                   // is the only permitted subtype of Foo.
                   // (A synthetic cast would be added for safety.)
MyFooImpl mfi = f; // 允许,因为编译器认为MyFooImpl
                   // 是唯一允许的Foo子类型。
                   // (为了安全起见,将添加合成转换。)

Alternatively, we could provide a new form of cast, for example:

或者,我们可以提供一种新形式的转换,例如:

MyFooImpl mfi = (total MyFooImpl) f;
MyFooImpl mfi = (total MyFooImpl) f;

In both cases, should the interface Foo be changed to permit another implementation, then both would cause compile-time errors upon recompilation.

在这两种情况下,如果接口Foo被更改为允许另一个实现,那么在重新编译时都会导致编译时错误。

Alternatives

Some languages have direct support for algebraic data types (ADTs), such as Haskell's data feature. It would be possible to express ADTs more directly and in a manner familiar to Java developers through a variant of the enum feature, where a sum of products could be defined in a single declaration. However, this would not support all the desired use cases, such as those where sums range over classes in more than one compilation unit, or sums that range over classes that are not products.

备选方案

某些语言直接支持代数数据类型(ADT),例如Haskell的data特性。通过enum特性的变体,可以更直接地以Java开发人员熟悉的方式表达ADT,其中可以在单个声明中定义sum和product。但是,这不会支持所有所需的用例,例如sum范围跨越多个编译单元中的类,或者sum范围跨越非product类的那些用例。

The permits clause allows a sealed class, such as the Shape class shown earlier, to be accessible-for-invocation by code in any module, but accessible-for-implementation by code in only the same module as the sealed class (or same package if in the unnamed module). This makes the type system more expressive than the access control system. With access control alone, if Shape is accessible-for-invocation by code in any module (because its package is exported), then Shape is also accessible-for-implementation in any module; and if Shape is not accessible-for-implementation in any other module, then Shape is also not accessible-for-invocation in any other module.

permits子句允许密封类,例如前面显示的Shape类,可以由任何模块中的代码访问以进行调用,但只能由与密封类(或相同包,如果在未命名的模块中)。这使得类型系统比访问控制系统更具表现力。单独使用访问控制,如果Shape可以被任何模块中的代码访问以调用(因为它的包被导出),那么Shape在任何模块中也可以被访问以实现;如果Shape在任何其他模块中不可访问以进行实现,那么Shape在任何其他模块中也不可访问以进行调用。

Dependencies

Sealed classes do not depend on any other JEPs. As mentioned earlier, JEP 406 proposes to extend switch with pattern matching, and builds on sealed classes to improve the exhaustiveness checking of switch.

依赖

密封类不依赖于任何其他JEP。如前所述,JEP 406建议使用模式匹配来扩展switch,并建立在密封类上以改进switch的详尽检查。