> De: "Brian Goetz" <brian.go...@oracle.com> > À: "amber-spec-experts" <amber-spec-experts@openjdk.java.net> > Envoyé: Vendredi 7 Décembre 2018 17:38:53 > Objet: Re: Sealed types
> I’ve updated the document on sealing to reflect the discussion so far. > Sealed Classes > Definition. A sealed type is one for which subclassing is restricted according > to guidance specified with the type’s declaration; finality can be considered > a > degenerate form of sealing, where no subclasses at all are permitted. Sealed > types are a sensible means of modeling algebraic sum types in a nominal type > hierarchy; they go nicely with records ( algebraic product types ), though are > also useful on their own. > Sealing serves two distinct purposes. The first, and more obvious, is that it > restricts who can be a subtype. This is largely a declaration-site concern, > where an API owner wants to defend the integrity of their API. The other is > that it potentially enables exhaustiveness analysis at the use site when > switching over sealed types (and possibly other features.) This is less > obvious, and the benefit is contingent on some other things, but is valuable > as > it enables better compile-time type checking. > Declaration. We specify that a class is sealed by applying the final modifier > to > a class, abstract class, interface, or record, and specifying a permits list: > final interface Node > permits A, B, C { ... } > In this explicit form, Node may be extended only by the types enumerated in > the > permits list (which must further be members of the same package or module.) > In many situations, this may be overly explicit; if all the subtypes are > declared in the same compilation unit, we may wish to permit a streamlined > form > of the permits clause, that means “may be extended by classes in the same > compilation unit.” > final interface Node > permits __nestmates { ... } > (As usual, pseudo-keywords beginning with __ are placeholders to illustrate > the > overall shape of the syntax.) > We can think of the simpler form as merely inferring the full permits clause > from information already present in the source file. > Anonymous subclasses (and lambdas) of a sealed type are prohibited. I suppose that by anonymous, you mean classes defined in methods named or not. Not supporting anonymous classes still seems backward to me. By example, this doesn't work: final interface Option<T> { default T orElseThrow() { throw new NoSuchElementException(); } public static <T> Option<T> empty() { return (Option<T>)Empty.EMPTY; } public static <T> Option<T> some(T value) { return new Option<>() { public T orElseThrow() { return value; } }; } } private class Empty<T> implements Option<T> { private static final Empty<?> EMPTY = new Empty<>(); } while this works: final interface Option<T> { default T orElseThrow() { throw new NoSuchElementException(); } public static <T> Option<T> empty() { return (Option<T>)Empty.EMPTY; } public static <T> Option<T> some(T value) { return new Some<>(value); } } private class Empty<T> implements Option<T> { private static final Empty<?> EMPTY = new Empty<>(); } private class Some<T> implements Option<T> { private final T value; public Some(T value) { this.value = value; } public T orElseThrow() { return value; } } basically forcing me to write the code that the compiler generates for me when i use an anonymous class. So sorry to be stubborn about that issue but why a stable name is a requirement given that private classes are supported ? > Exhaustiveness. One of the benefits of sealing is that the compiler can > enumerate the permitted subtypes of a sealed type; this in turn lets us > perform > exhaustiveness analysis when switching over patterns involving sealed types. > (In the simplified form, the compiler computes the permits list by enumerating > the subtypes in the nest when the nest is declared, since they are in a single > compilation unit.) > Note: It is superficially tempting to say permits package or permits module > as a > shorthand, which would allow for a type to be extended by package-mates or > module-mates without listing them all. However, this would undermine the > compiler’s ability to reason about exhaustiveness, because packages and > modules > are not always co-compiled. This would achieve the desired subclassing > restrictions, but not the desired ability to reason about exhaustiveness. yes ! > Classfile. In the classfile, a sealed type is identified with an ACC_FINAL > accessibility bit, and a PermittedSubtypes attribute which contains a list of > permitted subtypes (similar in structure to the nestmate attributes.) Classes > with ACC_FINAL but without PermittedSubtypes behave like traditional final > classes. > Sealing is inherited. Unless otherwise specified, abstract subtypes of sealed > types are implicitly sealed, and concrete subtypes are implicitly final. This > can be reversed by explicitly modifying the subtype with non-final . <bikeshed> we may re-use 'open' as dual of final here ? </bikeshed> > Unsealing a subtype in a hierarchy doesn’t undermine all the benefits of > sealing, because the (possibly inferred) set of explicitly permitted subtypes > still constitutes a total covering. However, users who know about unsealed > subtypes can use this information to their benefit (much like we do with > exceptions today; you can catch FileNotFoundException separately from > IOException if you want, but don’t have to.) > Note: Scala made the opposite choice with respect to inheritance, requiring > sealing to be opted into at all levels. This is widely believed to be a source > of bugs; it is relatively rare that one actually wants a subtype of a sealed > type to not be sealed, and in those cases, is best to be explicit. Not > inheriting would be a simpler rule, but I’d rather not add to the list of > “things for which Java got the defaults wrong.” > An example of where explicit unsealing (and private subtypes) is useful can be > found in the JEP-334 API: > final interface ConstantDesc > permits String, Integer, Float, Long, Double, > ClassDesc, MethodTypeDesc, MethodHandleDesc, > DynamicConstantDesc { } > final interface ClassDesc extends ConstantDesc > permits PrimitiveClassDescImpl, ReferenceClassDescImpl { } > private class PrimitiveClassDescImpl implements ClassDesc { } > private class ReferenceClassDescImpl implements ClassDesc { } > final interface MethodTypeDesc extends ConstantDesc > permits MethodTypeDescImpl { } > final interface MethodHandleDesc extends ConstantDesc > permits DirectMethodHandleDesc, MethodHandleDescImpl { } > final interface DirectMethodHandleDesc extends MethodHandleDesc > permits DirectMethodHandleDescImpl { } > // designed for subclassing > non-final class DynamicConstantDesc extends ConstantDesc { ... } > Enforcement. Both the compiler and JVM should enforce sealing, as they both > enforce finality today (though from a project-management standpoint, it might > be allowable for VM support to follow in a later version, rather than delaying > the feature entirely.) > Accessibility. Subtypes need not be as accessible as the sealed parent. In > this > case, some clients are not going to get the chance to exhaustively switch over > them; they’ll have to make these switches exhaustive with a default clause or > other total pattern. When compiling a switch over such a sealed type, the > compiler can provide a useful error message (“I know this is a sealed type, > but > I can’t provide full exhaustiveness checking here because you can’t see all > the > subtypes, so you still need a default.”) > Javadoc. The list of permitted subtypes should be incorporated into the > Javadoc. > Note that this is not exactly the same as the current “All implementing > classes” list that Javadoc currently includes, so a list like “All permitted > subtypes” might be added (possibly with some indication if the subtype is less > accessible than the parent, or including an annotation that there exist others > that are not listed.) > Open question: With the advent of records, which allow us to define classes > in a > single line, the “one class per file” rule starts to seem both a little silly, > and constrain the user’s ability to put related definitions together (which > may > be more readable) while exporting a flat namespace in the public API. I think > it is worth considering relaxing this rule to permit for sealed classes, say: > allowing public auxilliary subtypes of the primary type, if the primary type > is > public and sealed. i think we should relax the rule for any hierarchy not only the sealed one. Rémi