Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Multiple Java 8 Enums with same methods

Tags:

java

enums

java-8

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)
}
like image 403
simpleuser Avatar asked Sep 07 '25 20:09

simpleuser


1 Answers

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.

like image 64
Holger Avatar answered Sep 10 '25 07:09

Holger