I have a series of enums which look like this except that the name and values are different:
/* Bone Diagnosis. Value is internal code stored in database. */
public enum BoneDiagnosis {
NORMAL(121),
ELEVATED(207),
OSTEOPENIA(314),
OSTEOPOROSIS(315);
private int value;
BoneDiagnosis(final int value) {
this.value = value;
}
/** Get localized text for the enumeration. */
public String getText() {
return MainProgram.localize(this.getClass().getSimpleName().toUpperCase() + ".VALUE." + this.name());
}
/** Convert enumeration to predetermined database value. */
public int toDB() {
return value;
}
/** Convert a value read from the database back into an enumeration. */
public static BoneDiagnosis fromDB(final Integer v) {
if (v != null) {
for (final BoneDiagnosis pc : values()) {
if (v == pc.toDB()) {
return pc;
}
}
}
return null;
}
}
I know I cannot extend enums, but is there some way to abstract this design to remove all the duplicate code in toDB(), fromDB(), and getText() that each class has? I looked at other questions like Is it possible to extend enum in Java 8? which had an example using an interface, but I could not figure out how to handle the constructor and the static method. I also cannot figure out how to remove the explicit reference to type BoneDiagnosis in the fromDB() method.
My dream would be to have each class merely be defined something like what follows, with all the other support wrapped up in whatever BoneDiagnosisComplexTypeDefinition is. Is this possible?
public enum BoneDiagnosisComplexTypeDefinition {
NORMAL(121),
ELEVATED(207);
OSTEOPENIA(314),
OSTEOPOROSIS(315)
}
You can minimize the per-enum
code and the per-operation overhead using
@Target(ElementType.FIELD) @Retention(RetentionPolicy.RUNTIME)
public @interface DbId {
int value();
}
final class Helper extends ClassValue<Map<Object,Object>> {
static final Helper INSTANCE = new Helper();
@Override protected Map<Object, Object> computeValue(Class<?> type) {
Map<Object,Object> m = new HashMap<>();
for(Field f: type.getDeclaredFields()) {
if(f.isEnumConstant()) try {
Object constant = f.get(null);
Integer id = f.getAnnotation(DbId.class).value();
m.put(id, constant);
m.put(constant, id);
}
catch(IllegalAccessException ex) {
throw new IllegalStateException(ex);
}
}
return Collections.unmodifiableMap(m);
}
}
public interface Common {
String name();
Class<? extends Enum<?>> getDeclaringClass();
default int toDB() {
return (Integer)Helper.INSTANCE.get(getDeclaringClass()).get(this);
}
default String getText() {
return MainProgram.localize(
getDeclaringClass().getSimpleName().toUpperCase() + ".VALUE." + name());
}
static <T extends Enum<T>&Common> T fromDB(Class<T> type, int id) {
return type.cast(Helper.INSTANCE.get(type).get(id));
}
}
public enum BoneDiagnosis implements Common {
@DbId(121) NORMAL,
@DbId(207) ELEVATED,
@DbId(314) OSTEOPENIA,
@DbId(315) OSTEOPOROSIS;
}
Test example
int id = BoneDiagnosis.OSTEOPENIA.toDB();
System.out.println("id = " + id);
BoneDiagnosis d = Common.fromDB(BoneDiagnosis.class, id);
System.out.println("text = " + d.getText());
Note that the reflective operations are only performed once per class using ClassValue
which is especially designed for caching per-class meta data efficiently, thread safe and without preventing class unloading in environments where it matters. The actual toDB
and fromDB
are reduced to a hash lookups.
By the way, it’s important that this code uses getDeclaringClass()
rather than getClass()
as enums may have specializations like in enum Foo { BAR { … } … }
where getClass()
returns the specialization class rather than the enum
type.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With