【问题标题】:Multiple Java 8 Enums with same methods具有相同方法的多个 Java 8 枚举
【发布时间】:2018-02-16 16:02:25
【问题描述】:

我有一系列类似这样的枚举,只是名称和值不同:

/* 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;
    }
}

我知道我不能扩展枚举,但是有什么方法可以抽象出这个设计来删除每个类都有的 toDB()、fromDB() 和 getText() 中的所有重复代码?我查看了其他问题,例如Is it possible to extend enum in Java 8?,其中有一个使用接口的示例,但我不知道如何处理构造函数和静态方法。我也无法弄清楚如何在 fromDB() 方法中删除对 BoneDiagnosis 类型的显式引用。

我的梦想是让每个类只定义如下内容,而所有其他支持都包含在 BoneDiagnosisComplexTypeDefinition 中。这可能吗?

public enum BoneDiagnosisComplexTypeDefinition {
    NORMAL(121),
    ELEVATED(207);
    OSTEOPENIA(314),
    OSTEOPOROSIS(315)
}

【问题讨论】:

  • 组合优于继承”。创建一个 EnumIndexTransformer 类,您可以将其组合到您的 enum 中,然后委托给它。你的 Enum 也可以是 implementsinterface - 有一个 Indexable<I> 来公开这些方法。
  • 您的代码将无法编译。这 ;必须在 OSTEOPOROSIS 之后而不是在 ELEVATED 之后
  • @DwB 抱歉,我在修改代码以保护无辜者和客户时引入了一个错字
  • 我建议如果你有一个广泛的枚举层次结构,一个更好的模式可能是创建一个具有你需要的任何层次行为的实体层次结构,并将值存储在一个持久存储中。

标签: java enums java-8


【解决方案1】:

您可以使用以下方法最小化每个enum 代码和每个操作的开销

@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;
}

测试示例

int id = BoneDiagnosis.OSTEOPENIA.toDB();
System.out.println("id = " + id);
BoneDiagnosis d = Common.fromDB(BoneDiagnosis.class, id);
System.out.println("text = " + d.getText());

请注意,反射操作仅使用ClassValue 对每个类执行一次,它专为高效缓存每个类元数据而设计,线程安全,并且不会阻止在重要的环境中卸载类。实际的 toDBfromDB 被简化为哈希查找。

顺便说一句,这段代码使用getDeclaringClass() 而不是getClass() 很重要,因为枚举可能具有像enum Foo { BAR { … } … } 这样的特化,其中getClass() 返回特化类而不是enum 类型。

【讨论】:

  • ClassValue 的东西很花哨。我仍然觉得很有趣,即使在使用 Java 多年之后,还有新事物。
【解决方案2】:

接口方法是唯一可行的方法。这是另一种利用接口来最小化枚举中的代码重复的方法。

更新:

有些人抱怨反射和投射。这里有两个不需要强制转换的选项和一个不需要反射的选项。

Option1(清除无反射,但需要枚举中的额外方法):

 public static interface BoneDiagnosisType{

    public String name();

    public int getValue();

    default int toDB() {
        return getValue();
    }

    default String getText(){
        return MainProgram.localize( this.getClass().getSimpleName().toUpperCase() + ".VALUE." + name() );            
    }


    public static < E extends Enum<E> & BoneDiagnosisType > E fromDB(Class<E> eClass, Integer v) {
        if (v != null) {
            for ( final E pc : eClass.getEnumConstants() ) {
                if ( v == pc.toDB() ) {
                    return pc;
                }
            }
        }
        return null;
    } 
}

public static enum BoneDiagnosis1 implements BoneDiagnosisType{
    NORMAL(121),
    ELEVATED(207),
    OSTEOPENIA(314),
    OSTEOPOROSIS(315);

    int value;

    BoneDiagnosis1(int value) {
        this.value = value;
    }

    public int getValue(){
        return value;
    }
}

public static enum BoneDiagnosis2 implements BoneDiagnosisType{
    NORMAL(1121),
    ELEVATED(1207),
    OSTEOPENIA(1314),
    OSTEOPOROSIS(1315);

    int value;

    BoneDiagnosis2(int value) {
        this.value = value;
    }

    public int getValue(){
        return value;
    }
}

选项2(要求反射使枚举尽可能简单):

public static interface BoneDiagnosisType{

        public String name();

        default int toDB() {
            try{
                Class<?> clazz = getClass();            
                Field field = clazz.getDeclaredField("value");
                return field.getInt(this);
            }catch(RuntimeException e){
                throw e;
            }catch(Exception e){
                throw new RuntimeException(e);
            }
        }

        default String getText(){
            return MainProgram.localize( this.getClass().getSimpleName().toUpperCase() + ".VALUE." + name() );            
        }


        public static < E extends Enum<E> & BoneDiagnosisType > E fromDB(Class<E> eClass, Integer v) {
            if (v != null) {
                for ( final E pc : eClass.getEnumConstants() ) {
                    if ( v == pc.toDB() ) {
                        return pc;
                    }
                }
            }
            return null;
        }  
    }


    public static enum BoneDiagnosis1 implements BoneDiagnosisType{
        NORMAL(121),
        ELEVATED(207),
        OSTEOPENIA(314),
        OSTEOPOROSIS(315);

        int value;

        BoneDiagnosis1(int value) {
            this.value = value;
        }
    }

    public static enum BoneDiagnosis2 implements BoneDiagnosisType{
        NORMAL(1121),
        ELEVATED(1207),
        OSTEOPENIA(1314),
        OSTEOPOROSIS(1315);

        int value;

        BoneDiagnosis2(int value) {
            this.value = value;
        }
    }

以及示例打印输出:

System.out.println( BoneDiagnosis1.NORMAL.toDB() + " : " + BoneDiagnosis1.NORMAL.getText() + " : " + 
BoneDiagnosisType.fromDB( BoneDiagnosis1.class, 121 ) );

System.out.println( BoneDiagnosis1.ELEVATED.toDB() + " : " + BoneDiagnosis1.ELEVATED.getText() + " : " + 
BoneDiagnosisType.fromDB( BoneDiagnosis1.class, 207 ) );

System.out.println( BoneDiagnosis2.NORMAL.toDB() + " : " + BoneDiagnosis2.NORMAL.getText() + " : " + 
BoneDiagnosisType.fromDB( BoneDiagnosis2.class, 1121 ) );

System.out.println( BoneDiagnosis2.ELEVATED.toDB() + " : " + BoneDiagnosis2.ELEVATED.getText() + " : " + 
BoneDiagnosisType.fromDB( BoneDiagnosis2.class, 1207 ) );

将给予:

121:BONEDIAGNOSIS1.VALUE.NORMAL:正常

207:BONEDIAGNOSIS1.VALUE.ELEVATED:升高

1121:BONEDIAGNOSIS2.VALUE.NORMAL:正常

1207:BONEDIAGNOSIS2.VALUE.ELEVATED:升高

【讨论】:

  • 这很整洁。概括地说,为什么需要 eClass 作为 fromDB() 的参数?它与 有何不同?像 BoneDiagnosis2.fromDB(intval) 这样的东西是不可能的吗?你能解释一下需要“ & BoneDiagnosisType >”吗? BoneDiagnosisType 已经扩展了类型“E extends Enum”,那么为什么需要两个边界?
  • 我们需要eClass的原因是该方法是静态的,我们无法知道要调用哪个枚举values方法。通过使用该类,我们可以使用class.getEnumConstants 完成values 所做的事情。我们在泛型中需要这两种类型的原因是因为我们正在使用不是枚举的BoneDiagnosisType 接口(我们不能扩展枚举)。两者都使用将确保 class.getEnumConstants 将给我们 BoneDiagnosisType 而无需强制转换。
  • 我现在明白了;乍一看,因为它是一个接口,我感到困惑,并认为 fromDB() 是 BONEDIAGNOSIS2 类型的静态实例以及 BONEDIAGNOSIS1 中的另一个实例,但它只是 BoneDiagnosisType 的静态部分。谢谢!
  • 这充斥着糟糕的编程习惯:setAccessible 的使用;枚举类的作者看不到的反射的使用;使用“catch (Exception e)”代替catch (ReflectiveOperationException e);使用(E) this 而不仅仅是+ this(以及由此产生的@SuppressWarnings 需求)。这在功能上可能是正确的,但它是一个糟糕的解决方案。
  • @simpleuser。我确实用 2 个不需要强制转换的选项和一个也不需要反射并且是首选方法的选项更新了我的答案。
【解决方案3】:

这是另一种方法,您可以使用一些人所说的“虚拟字段模式”来解决这个问题。它将每个枚举的重复代码量减少到一个 getter 和一个字段。它还避免了反射。

首先为您的枚举共有的所有方法创建一个接口。在本例中,我们将其命名为 Common

public interface Common {

    int getId();

    String getName();
}

创建另一个扩展Common 的接口。这将只有一个抽象方法,它只返回一个Common 的实例。为其他方法提供委托给Common 实例的默认实现。

public interface VirtualCommon extends Common {

    Common getCommon();

    @Override
    default int getId() {
        return getCommon().getId();
    }

    @Override
    default String getName() {
        return getCommon().getName();
    }
}

现在创建Common 的具体实现。

public class CommonImpl implements Common {

    private int id;
    private String name;

    public CommonImpl(int id, String name) {
        this.id = id;
        this.name = name;
    }

    @Override
    public int getId() {
        return this.id;
    }

    @Override
    public String getName() {
        return this.name;
    }
}

或者,如果您想节省几行代码,您可以在 Common 上放置一个返回匿名类的静态方法,而不是 CommonImpl

static Common of(final int id, final String name) {
    return new Common() {
        @Override
        public int getId() {
            return id;
        }

        @Override
        public String getName() {
            return name;
        }
    };
}

现在您可以创建每个枚举,它们只需要一个字段和一个 getter。任何实现VirtualCommon 的类或枚举都将成为 Common,因为它拥有 Common

public enum EnumImpl implements VirtualCommon {

    ALPHA(1, "Alpha"),
    BETA(2, "Beta"),
    DELTA(3, "Delta"),
    GAMMA(4, "Gamma");

    private final Common common;

    EnumImpl(int id, String name) {
        this.common = new CommonImpl(id, name);
    }

    @Override
    public Common getCommon() {
        return this.common;
    }
}

【讨论】:

    【解决方案4】:

    此解决方案仍有一些样板桥接代码。

    使用Interface 定义调用接口并在某个类中实现公共代码。 这是一个简单的例子:

    文件:BoneDiagnosis.java

    public enum BoneDiagnosis
    implements
        CommonStuffs
    {
      NORMAL(121),
      ELEVATED(207),
      OSTEOPENIA(314),
      OSTEOPOROSIS(315);
    
      private CommonStuffsImpl commonStuffsImpl;
      private int value;
    
      BoneDiagnosis(final int theValue)
      {
          value = theValue;
    
          commonStuffsImpl = new CommonStuffsImpl();
      }
    
      @Override
        public int toDB()
      {
        return commonStuffsImpl.toDBImplementation(value);
      }
    }
    

    文件:CommonStuffs.java

    public interface CommonStuffs
    {
        int toDB();
    }
    

    文件:CommonStuffsImpl.java

    public class CommonStuffsImpl
    {
        public int toDBImplementation(
            final int value)
        {
            return value;
        }
    }
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2013-09-23
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2013-05-16
      相关资源
      最近更新 更多