Experience with sealed classes & the "same package" rule

Maurizio Cimadamore maurizio.cimadamore at oracle.com
Mon Jun 21 13:10:25 UTC 2021

I understand the issue you bring up here. I generally agree that having 
a rules which depend on whether an application or library is modularized 
or not, seems unfortunate.


When working with modules, it is a fairly common practice to structure 
applications are libraries in multiple set of packages - a package 
containing the publicly exported types by a given module, and another 
package containing the "implementation types". I find this way of 
splitting much more natural than dumping everything into the same 
package and rely on access modifiers to only expose what I want, and, we 
use this pattern extensively in the Panama API.

So, in this sense, the fact that the "sealed" modifier has special 
treatment for module is, IMHO, *not* an incentive to use module, as much 
as a _forced_ move to make sure that sealed remains useful in a 
modularized application/library.

Could we live w/o that rule? Sure we probably could, but let's not 
assume that dropping that rule is "free": the Panama API/impl would have 
to be refactored pretty heavily to take advantage of sealing w/o that 
rule (while, with current rules we could just drop "sealed" where it 
belongs, and everything just works).

To summarize, playing nice w.r.t. modules has a cost (which Dan email 
summarizes well) and benefits (the ones which I try to capture here). We 
should decide whether to keep vs. reject the sealed module rule only if 
we conclude that the cost for supporting that rule outweights the 
benefits. And, from what I've heard, I'm not sure that's clearly the 
case now.


On 09/06/2021 22:42, Dan Smith wrote:
> Here's some late feedback on the sealed classes preview. Probably actionable before 17 GA, if that's what people want to do, or potentially a change that can come in a future release. (Or you can just tell me that this inconvenience isn't a big deal, live with it.)
> The spec for sealed classes contains this rule:
> ---
> If a sealed class C belongs to a named module, then every class named in the permits clause of the declaration of C must belong to the same module as C; otherwise a compile-time error occurs.
> If a sealed class C belongs to an unnamed module, then every class named in the permits clause of the declaration of C must belong to the same package as C; otherwise a compile-time error occurs.
>> Sealed class hierarchies are not intended to be declared across different maintenance domains. Modules cannot depend on each other in a circular fashion, yet a sealed class and its direct subclasses need to refer to each other in a circular fashion (in permits and extends clauses, respectively). Necessarily, therefore, a sealed class and its direct subclasses must co-exist in the same module. In an unnamed module, a sealed class and its direct subclasses must belong to the same package.
> ---
> A unique property of this rule is that it is guarded on whether code lives in a (named) module or not. If you're in a module, you get language semantics X; if not, you get language semantics Y. In particular, there exist programs that will compile when packaged in a module, but will not when left to the unnamed module. (This is different than the canonical use of modules, which is to restrict the set of packages that are observable, depending on the configuration of the current module.)
> In practice, I had this experience:
> 1) Trying to deploy some Java code with a tool that requires a single jar.
> 2) Need to include the experimental bytecode API, which is packaged as a module.
> 3) Copy the needed API sources into my own source tree.
> 4) Compilation fails: lots of "cannot extend a sealed class in a different package" errors.
> Unfortunately, the bytecode API was making use of sealed classes, and those hierarchies crossed package boundaries. (In a typical public API vs. private implementation packaging strategy.)
> (The workaround was to declare my own module-info.java and package my program as a single modular jar.)
> Is this behavior consistent with the design of modules? I lean towards "no": Java sources don't opt in to modules. They belong to a module, or not, depending on the context—whether there's a module-info.java somewhere, which javac parameters are used. The sources themselves are meant to be portable. But this rule means any sealed class hierarchy that crosses package boundaries is asserting *in the program source* that the classes must belong to a module.
> In practice, I think it's probably a fairly routine experience to take some sources, originally written in a module context, and recompile them in a module-free context. I did it because of idiosyncrasies of my deployment scenario. Others might do it because, say, they're releasing the same API for different audiences.
> Proposed solution: just drop the whole rule I quoted above. Sealed classes and their children already must be able to "see" each other, so it's impossible to consistently compile the hierarchy in separate maintenance domains. There are ways you could bootstrap your way into it with separate compilation in the unnamed module, but... if you're trying that hard, you probably know what you're doing. It's hard for me to envision *accidentally* creating a sealed hierarchy across maintenance domains.
> (Or, if we like explicitly stating the same-module rule, even though it's redundant, that would be okay. Just drop the same-package rule in this scenario, and apply the same standard to named and unnamed modules.)

More information about the amber-spec-experts mailing list