[sealed] Sealed interface as a utility class?
Brian Goetz
brian.goetz at oracle.com
Fri Oct 18 14:16:54 UTC 2019
In the “things to tweak about interfaces” department, I would place a higher priority on aligning the restrictions on member accessibility — right now, we can have private methods, but not private member classes (or private fields, though that’s less of a motivation.)
I have mixed feelings about the proposal here. On the one hand, it is motivated by the right thoughts — making it easy for users to follow a pattern to reliably do a common thing. On the other, I don’t particularly like “language design by design pattern” — it feels too roundabout.
Stepping back, the proposal seems to really be about _instance control_. Both the “singleton” and (warning, I’m gonna make up a silly word) “nonegleton” patterns are useful, but require workarounds to implement. (Singleton requires either abuse of enums, or a private field with a public caching accessor; “nonegleton” requires a constructor that no code is ever intended to call. These are like the current workaround for sealed types, where we can have the effect of sealed classes if the constructor is not widely accessible.) I share your distaste for code constructs that are intended to never be used, and are there entirely for their “side effects.”
Scala approaches the singleton pattern with the `object` abstraction; an `object` is a class with exactly one instance. It is a more principled approach than static members, in that everything is still an object, and `object` can implement interfaces. (Also, language support for singleton subsumes nonegleton reasonably well.) As a syntactic bonus, you don’t have to say “static” when declaring members.
Singleton objects in Scala also also play a role in the implicits story, acting as “service providers” for things like factories. There are some items on the longer-term roadmap that may have some overlap here as well.
All this said, I think there’s something missing here — most likely, something for managing instance control of classes and interfaces alike — but I’m not inclined to move it to the front of the queue just because sealed types are currently in play. But I think its something worth continuing to think about.
> On Oct 18, 2019, at 5:02 AM, Tagir Valeev <amaembo at gmail.com> wrote:
>
> Hello!
>
> A utility class (a class that has only static members) is a ubiquitous
> Java pattern. They are used in JDK, common third-party libraries and
> applications. Usually, it's a final class. Since Java 8 (static
> methods were introduced in the interface) it's possible to convert a
> utility class to an interface. There are a number of advantages:
>
> - It cannot be instantiated, by interface definition. As a result, you
> don't need to create an ugly constructor like `private MyUtils()
> {throw new UnsupportedOperationException();}`
> - If you care much about code coverage, now you would have very bad
> times trying to cover this constructor (some people do this!). Declare
> it as an interface, and the problem is gone.
> - No need to specify `static final` on the fields -- less visual noise
> - No need to specify `static` on nested classes -- less visual noise
> - You cannot accidentally declare a non-static member: it will be a
> compilation error, and IDE will highlight it immediately suggesting a
> fix. My experience shows that people indeed sometimes mistakenly
> declare a non-static member in a utility class and realize this only
> on the first usage. This is frustrating (why I don't see the
> completion option? I just added this method, I know it's there! Is my
> IDE drunk?) and takes time to fix.
> - You cannot accidentally declare a non-static field or a non-static
> nested class. You simply cannot.
>
> Unfortunately one may implement such an interface which is not what we
> would like to allow. In fact, such an approach was used in early Java
> versions (until import static was introduced in Java 5) to use
> interfaces that declare only constants (this is a subset of utility
> classes). Now it's considered an anti-pattern to implement such an
> interface [1].
>
> Having sealed classes we may declare such an interface as a sealed and
> provide no implementations at all, making it effectively final.
> Unfortunately (or not), the latest spec draft [2] explicitly disables
> this (9.1.4):
>
>> It is a compile-time error if a sealed interface has no permitted subtypes.
>
> This could be worked around introducing a bogus implementor inside:
>
> public sealed interface MyUtils {
> private final class X implements MyUtils {} // just to make a compiler happy
> // static members follow
> }
>
> This will undermine the first advantage, as the bogus class should
> again have the throwing constructor. However, you still have less
> visual noise, and you've got protection against an accidental
> non-static member. So we may expect that people will (ab)use this
> pattern.
>
> The question is: whether should we support this or not? If yes, we
> could lift the restriction on sealed interfaces without permits (which
> essentially allows creating final interfaces). We could go further and
> while allowing such interfaces we may disable any instance or default
> methods inside (as nobody could call them anyway) which would make the
> thing even more robust, and everybody would know that final interface
> is a utility class.
>
> If we don't support this, probably we should spell this out somewhere.
> If somebody going to write a programmer's guide to sealed classes
> (similar to TextBlocks one [3]), probably it's a good point to
> mention.
>
> A good alternative would be to invent a special syntax for utility
> classes like `utlity class MyUtils {}`. This adds a few restrictions:
> - All members are implicitly static; static modifier is allowed but
> not necessary
> - All fields are implicitly final, like in interfaces
> - Constructors and instance initializers are disallowed
> - Inheritors and instantiation is disallowed
> - A `utility` modifier could be obtained via reflection
> This would be a nice alternative, and there will be no reason to abuse
> other language features.
>
> With best regards,
> Tagir Valeev.
>
> [1] https://en.wikipedia.org/wiki/Constant_interface
> [2] http://cr.openjdk.java.net/~gbierman/jep360/jep360-20190830/specs/sealed-types-jls.html
> [3] https://cr.openjdk.java.net/~jlaskey/Strings/TextBlocksGuide_v9.html
More information about the amber-spec-experts
mailing list