[sealed] Sealed interface as a utility class?
Maurizio Cimadamore
maurizio.cimadamore at oracle.com
Fri Oct 18 13:47:32 UTC 2019
Hi Tagir,
I think think that what you are asking for is more along the lines of
having "final interface" rather than sealed which permits nobody. Right?
I agree that it could be worth re-examining the restriction that
interfaces cannot be marked as final; I think that restriction comes
from way back; if interfaces are just there to specify a contract which
all implementation should adhere to, a final interface is a bit of an
oxymoron.
But after the recent addition to the language, the distinction between
classes and interfaces has gotten quite thin:
- interfaces can have default implementations
- interfaces can have static methods
- interfaces can have private methods
So, it is conceivable that one might now want to use an interface not to
specify behavior, but, more simply, to define a bad of associate static
methods. In such a case, marking the interface as 'final' makes perfect
sense.
So, I think the question here is more whether we're philosophically ok
with people using the "interface" construct in this fashion; one might
argue that this is, admittedly, quite far from what language designers
had in mind for interfaces in the first place; but at the same time, as
you also noted, this didn't stop developers from using interfaces to
define bags of constants and then having clients 'implement' them (to
inherit the constants) which was eventually replaced by the more stable
import static feature.
Maurizio
On 18/10/2019 10:02, Tagir Valeev 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