Switch on sealed enums
Matthias Perktold - ASA
matthias.perktold at asaon.com
Wed Nov 24 08:23:18 UTC 2021
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
More information about the amber-dev
mailing list