Switch on sealed enums
Gavin Bierman
gavin.bierman at oracle.com
Fri May 5 15:48:52 UTC 2023
Hi Matthias,
Just circling back to this now we have support for qualified enum constant labels in JEP 441 [1].
I’m happy to say that the “single, flattened switch” code now works as expected:
$ cat EnumTest.java
class EnumTest {
sealed interface Token {}
enum Digit implements Token { ONE, TWO, THREE }
enum Letter implements Token { A, B, C }
static String toStringFlat(Token token) {
return switch (token) {
case Digit.ONE -> "1";
case Digit.TWO -> "2";
case Digit.THREE -> "3";
case Letter.A -> "A";
case Letter.B -> "B";
case Letter.C -> "C";
};
}
public static void main(String[] args) {
System.out.println(toStringFlat(Digit.TWO));
System.out.println(toStringFlat(Letter.C));
System.out.println("End");
}
}
$ build/macosx-x86_64-server-release/images/jdk/bin/java EnumTest.java
2
C
End
Thanks for the feedback,
Gavin
[1] Pattern matching for switch: https://openjdk.org/jeps/441
On 24 Nov 2021, at 08:23, Matthias Perktold - ASA <matthias.perktold at asaon.com> wrote:
I've played around with sealed interfaces implemented by multiple enums only, and I've found some inconveniences I wanted to share here.
Consider the following class hierarchy:
sealed interface Token {
enum Digit implements Token { ONE, TWO, THREE }
enum Letter implements Token { A, B, C }
}
I can use a nested switch expression to cover all cases:
static String toStringNested(Token token) {
return switch (token) {
case Digit d -> switch (d) {
case ONE -> "1";
case TWO -> "2";
case THREE -> "3";
};
case Letter l -> switch (l) {
case A -> "A";
case B -> "B";
case C -> "C";
};
};
}
I thought that in theory, I should be able to use a single, flattened switch that covers all digits and all letters:
static String toStringFlat(Token token) {
return switch (token) {
case ONE -> "1";
case TWO -> "2";
case THREE -> "3";
case A -> "A";
case B -> "B";
case C -> "C";
};
}
This is probably deliberately not supported (yet). Anyway, here are my findings:
1. The compiler requires that the enum constants be unqualified.
But since we have multiple enums here, just leaving the enum away doesn't work either; we need to add static imports:
import static package.to.Token.Digit.*;
import static package.to.Token.Letter.*;
2. The compiler cannot figure out that the switch is exhaustive, even if we do cover all cases.
We need to add a default case to fix this compiler error.
3. With the default case added, the code still doesn't compile (my IDE thinks it does);
We get an error "constant expression required" for each case.
In case you wonder why one would use sealed hierarchies of enums, it probably makes more sense to think about these
as "enums with subgroups". Using this approach, you can model more precisely that a method accepts or returns only a subset
of the cases, but a logical coherent subset.
One special reason of grouping enum constants is the use of generics: we could extend our hierarchy as follows:
sealed interface Token<T> {
T value();
enum Digit implements Token<Integer> {
ONE, TWO, THREE;
@Override public Integer value() { ... }
}
enum Letter implements Token<Character> {
A, B, C;
@Override public Character value() { ... }
}
}
This could also be seen as a workaround for JEP 301: "Enhanced Enums", but a very good one in my opinion.
Only when every enum constant needs a different type argument (as in the JEP's examples), you would need to wrap each constant
in its own enum.
Overall, I think there are good places for such groupings of enums. And it would help if they could be used just like a single enum inside
switch.
Cheers,
Matthias Perktold
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <https://mail.openjdk.org/pipermail/amber-dev/attachments/20230505/96619860/attachment-0001.htm>
More information about the amber-dev
mailing list