Local classes & sealed classes

Dan Smith daniel.smith at oracle.com
Fri May 1 00:46:51 UTC 2020


There are some interesting interactions between local classes and sealed classes that I'm not sure are fully explored currently. Or maybe they are, and the spec is out of sync, more commentary is needed, etc. Here are some thoughts/questions about the interaction.

(BTW, I'm aware we're close to wanting to "freeze" the design for 15, and I'm totally fine with this discussion being set aside as feedback to consider for 16, if that's more convenient.)

---

1) A local class can extend a sealed class that lacks a 'permits' clause.

The implicit list of permitted subclasses will include all classes declared in the same compilation unit.

sealed class A {
    void m() {
        final class B extends A {}
    }
}

Note that the local class can't be declared 'sealed' or 'non-sealed'—see below—but can be declared 'final'.

There's nothing particularly wrong with this, other than the fact that it's impossible to rewrite it with an explicit 'permits' clause.

I can see it being a useful way to handle, say, an interface that declares a handful of factory methods to produce a limited set of implementations. 

---

2) A local class cannot be 'sealed' with an implicit 'permits' clause.

Because local classes aren't recursive (their scope only goes from their declaration onward), two peers can't refer to each via 'permits' and 'extends'. But an implicit 'permits' gets around that problem:

void m() {
    sealed class A {}
    final class B extends A {}
}

Again, I can see not wanting to support this because it's impossible to rewrite it with an explicit 'permits' clause.

On the other hand, it's pretty useful—throw in local interfaces and local records, and it's not hard to imagine a small sealed hierarchy sprouting up.

On the other other hand, the local-ness of the class already "seals" it to the declaring method, so there's not a lot to be gained by explicitly saying 'sealed', especially when you can't enumerate your permitted subclasses. The effects would be:
- Forcing all subclasses to be explicit about sealed/non-sealed/final
- Preventing extension by anonymous classes or lambdas
- Limiting what subclassing can be done at runtime via, e.g., bytecode spinning
- Anything else?

On the other^3 hand, Brian's crusade for uniform nesting will cjamge some of these assumptions, and programmers will want to play with sealed classes in a top-level main method.

---

3) A local class cannot be 'sealed', even by permitting its member classes.

void m() {
    sealed class A permits A.B {
        final class B extends A {}
    }
}

It's less clear to me why we wouldn't want to allow this. I mean, A can't be extended outside of m anyway, but 'sealed' here is about as useful as 'final' (which is allowed).

Again, not hard to imagine a sealed local interface with a family of member or local classes declared in its body.

---

4) A local class cannot be 'non-sealed'.

This one builds on (1). *If* it's fine to extend a sealed class with a final local class, then it makes sense to allow 'non-sealed', too.

sealed class A {
    void m() {
        non-sealed class B extends A {}
        class C extends B {}
    }
}

If we also allow B to be declared 'sealed' (as in (2)), then the distinction between using 'sealed' here and 'non-sealed' is pretty subtle (nobody outside the method can extend B in either case). It goes back to the effects I noted in (2)—'non-sealed' allows children to be declared without a modifier, allows anonymous/lambda children, and permits subclassing at runtime.

---

I think I lean towards generally being permissive in these cases—if the semantics make sense, why bother keeping people from doing it?—but I'm curious about others' opinions.



More information about the amber-spec-observers mailing list