You could also use an adaptation of the Visitor pattern to enums, which avoid putting all kind of unrelated state in the enum class.
The compile time failure will happen if the one modifying the enum is careful enough, but it is not garanteed.
You'll still have a failure earlier than the RTE in a default statement : it will fail when one of the visitor class is loaded, which you can make happen at application startup.
Here is some code :
You start from an enum that look like that :
public enum Status {
PENDING, PROGRESSING, DONE
}
Here is how you transform it to use the visitor pattern :
public enum Status {
PENDING,
PROGRESSING,
DONE;
public static abstract class StatusVisitor<R> extends EnumVisitor<Status, R> {
public abstract R visitPENDING();
public abstract R visitPROGRESSING();
public abstract R visitDONE();
}
}
When you add a new constant to the enum, if you don't forget to add the method visitXXX to the abstract StatusVisitor class, you'll have directly the compilation error you expect everywhere you used a visitor (which should replace every switch you did on the enum) :
switch(anObject.getStatus()) {
case PENDING :
[code1]
break;
case PROGRESSING :
[code2]
break;
case DONE :
[code3]
break;
}
should become :
StatusVisitor<String> v = new StatusVisitor<String>() {
@Override
public String visitPENDING() {
[code1]
return null;
}
@Override
public String visitPROGRESSING() {
[code2]
return null;
}
@Override
public String visitDONE() {
[code3]
return null;
}
};
v.visit(anObject.getStatus());
And now the ugly part, the EnumVisitor class. It is the top class of the Visitor hierarchy, implementing the visit method and making the code fail at startup (of test or application) if you forgot to update the absract visitor :
public abstract class EnumVisitor<E extends Enum<E>, R> {
public EnumVisitor() {
Class<?> currentClass = getClass();
while(currentClass != null && !currentClass.getSuperclass().getName().equals("xxx.xxx.EnumVisitor")) {
currentClass = currentClass.getSuperclass();
}
Class<E> e = (Class<E>) ((ParameterizedType) currentClass.getGenericSuperclass()).getActualTypeArguments()[0];
Enum[] enumConstants = e.getEnumConstants();
if (enumConstants == null) {
throw new RuntimeException("Seems like " + e.getName() + " is not an enum.");
}
Class<? extends EnumVisitor> actualClass = this.getClass();
Set<String> missingMethods = new HashSet<>();
for(Enum c : enumConstants) {
try {
actualClass.getMethod("visit" + c.name(), null);
} catch (NoSuchMethodException e2) {
missingMethods.add("visit" + c.name());
} catch (Exception e1) {
throw new RuntimeException(e1);
}
}
if (!missingMethods.isEmpty()) {
throw new RuntimeException(currentClass.getName() + " visitor is missing the following methods : " + String.join(",", missingMethods));
}
}
public final R visit(E value) {
Class<? extends EnumVisitor> actualClass = this.getClass();
try {
Method method = actualClass.getMethod("visit" + value.name());
return (R) method.invoke(this);
} catch (Exception e) {
throw new RuntimeException(e);
}
}
}
There are several ways you could implement / improve this glue code. I choose to walk up the class hierarchy, stop when the superclass is the EnumVisitor, and read the parameterized type from there. You could also do it with a constructor param being the enum class.
You could use a smarter naming strategy to have less ugly names, and so on...
The drawback is that it is a bit more verbose.
The benefits are
- compile time error [in most cases anyway]
- works even if you don't own the enum code
- no dead code (the default statement of switch on all enum values)
- sonar/pmd/... not complaining that you have a switch statement without default statement
default
case? – Freedmandefault
case, then it's unlikely that any compile-time tool would warn you about "missing" cases (because they're not actually missing in that scenario ;) ) – Freedmandefault
because this kind of default is always a run time error. Because if it would be a valid state it should have been an enum value. – Haasdefault:
case and write a test which inputs all possible enum values? – Heparin