Java枚举:实现通用方法,避免重复

Java enums: implementing common methods, avoiding duplication

假设我有两个不同的枚举

public enum SomeEnumClass {

    private static  final SomeEnumClass[]   mValues = SomeEnumClass .values();
    ONE(1), TWO(2), THREE(3);

}

public enum OtherEnumClass {
    private static  final OtherEnumClass[]  mValues = OtherEnumClass .values();
    Monday(1), Tuesday(2), Wednesday(3), Thrusday(4), Friday(5), Saturday(6), Sunday(7)

}

枚举在它们携带的数据类型(此处为 int)上具有共同点,并且在它们的名称和可能值的数量上有所不同。

对于这些枚举中的每一个,我都有几种实现方法,它们完全相同。示例:

        public static OtherEnumClass getCell(int index)
    {
        if (index < OtherEnumClass .mValues.length )
        {
            return OtherEnumClass .mValues[index];              
        }
        throw new IllegalArgumentException("Invalid " + OtherEnumClass .class.getSimpleName() + " value: " + index);
    }

我正试图找到一种方法来避免重复这些方法,就像我对抽象 类 所做的那样。但到目前为止,我一无所有。

我们正在使用 java 1.6,暂时无法升级。任何帮助表示赞赏。谢谢。

你可以这样做:

public enum SomeEnumClass {

    ONE, TWO, THREE;

}

public enum OtherEnumClass {

    Monday, Tuesday, Wednesday, Thrusday, Friday, Saturday, Sunday

}

public static <E extends Enum> E getEnumItem(Class<E> type, int index){
    E[] values = type.getEnumConstants();
    if (index >= 0 && index < values.length){
        return values[index];
    } else {
        throw new IllegalArgumentException("...");
    }
}

public static void main(String[] args) {
    System.out.println(getEnum(SomeEnumClass.class, 0));
    System.out.println(getEnum(OtherEnumClass.class, 3));
    System.out.println(getEnum(SomeEnumClass.class, 2));
    System.out.println(getEnum(OtherEnumClass.class, 6));
}

它打印:

ONE
Thrusday
THREE
Sunday

已编辑: 这与@dasblinkenlight

的想法类似
public enum SomeEnumClass {

    ONE, TWO, THREE;

    public static SomeEnumClass getCell(int index) {
        return Utility.getEnumItem(SomeEnumClass.class, index);
    }
}

public enum OtherEnumClass {

    Monday, Tuesday, Wednesday, Thrusday, Friday, Saturday, Sunday;

    public static OtherEnumClass getCell(int index) {
        return Utility.getEnumItem(OtherEnumClass.class, index);
    }
}

public static class Utility {

    public static <E extends Enum> E getEnumItem(Class<E> type, int index) {
        E[] values = type.getEnumConstants();
        if (index >= 0 && index < values.length) {
            return values[index];
        } else {
            throw new IllegalArgumentException("...");
        }
    }
}

public static void main(String[] args) {
    System.out.println(Utility.getEnumItem(SomeEnumClass.class, 0));
    System.out.println(Utility.getEnumItem(OtherEnumClass.class, 3));
    System.out.println(Utility.getEnumItem(SomeEnumClass.class, 2));
    System.out.println(Utility.getEnumItem(OtherEnumClass.class, 6));
}

您可以将您的实现包装到通用帮助器 class 中,并在您的所有实现中使用它。不幸的是,您必须将调用复制到助手中; Java 8 的默认方法解决了这个问题,但是你不能利用它们,因为你被限制在 Java 6.

// Helper owns the static members that you used to add to your enums directly
class CellHelper<T> {
    final T[] mValues;
    final Class<T> cls;
    // Helper needs Class<T> to work around type erasure
    public CellHelper(T[] values, Class<T> c) {
        mValues = values;
        cls = c;
    }
    public T getCell(int index) {
        if (index < mValues.length ) {
            return mValues[index];              
        }
        throw new IllegalArgumentException("Invalid " + cls.getSimpleName() + " value: " + index);
    }
}

enum SomeEnumClass {
    ONE(1), TWO(2), THREE(3);
    SomeEnumClass(int n){}
    // This variable hosts your static data, along with shared behavior
    private static  final CellHelper<SomeEnumClass> helper = new CellHelper(SomeEnumClass.values(), SomeEnumClass.class);
    // Delegate the calls for shared functionality to the helper object
    public static SomeEnumClass getCell(int i) {return helper.getCell(i);}
}

enum OtherEnumClass {
    Monday(1), Tuesday(2), Wednesday(3), Thrusday(4), Friday(5), Saturday(6), Sunday(7);
    OtherEnumClass(int n){}
    private static  final CellHelper<OtherEnumClass> helper = new CellHelper(OtherEnumClass.values(), OtherEnumClass.class);
    public static OtherEnumClass getCell(int i) {return helper.getCell(i);}
}

Demo.

您可以使用接口:

public interface Indexed<E extends Enum> {

    default public E getByIndex(int index) {
        if (!this.getClass().isEnum()) {
            //not implemented on enum, you can do as you like here
        }
        Enum<?>[] vals = (Enum<?>[]) this.getClass().getEnumConstants();
        if (index < 0 || index >= vals.length) {
            //illegal arg exception
        }
        return (E) vals[index];
    }

}

然后在执行中:

public enum MyEnum implements Indexed<MyEnum> {
    ONE,
    TWO,
    THREE,
    ;
}

另请注意,您可以只使用 Enum#ordinal.

而不是手动提供这些索引

这是一个仅 Java 8 的解决方案,因为在以前的 Java 版本中没有默认方法。此外,这有一些 awkward/disadvantageous 用法,因为它是一个实例方法(尽管如果需要,您可以将其设为静态)。

对于早期版本,您需要一种方法来提供 class 类型,因为实际上并没有不同的方法为枚举提供它,您可以在其中使用上面 David 的回答。

您的代码示例有点误导,因为它 returns 具有相同序数的常量而不是具有相同 属性 值的常量。为了抽象搜索具有 属性 值的常量,您必须抽象 属性,例如

interface TypeWithIntProperty {
  int getProperty();
}
enum Number implements TypeWithIntProperty {
  ONE(1), TWO(2), THREE(3);

  private final int value;

  Number(int value) {
    this.value=value;
  }
  public int getProperty() {
    return value;
  }
}
enum DayOfWeek implements TypeWithIntProperty {
  Monday(1), Tuesday(2), Wednesday(3), Thrusday(4), Friday(5), Saturday(6), Sunday(7);

  private final int value;

  DayOfWeek(int value) {
    this.value=value;
  }
  public int getProperty() {
    return value;
  }
}

public class Helper {
  public static <E extends Enum<E>&TypeWithIntProperty>
                E getEnumItem(Class<E> type, int value) {
    for(E constant: type.getEnumConstants())
      if(value == constant.getProperty())
        return constant;
    throw new IllegalArgumentException("no constant with "+value+" in "+type);
  }
}

DayOfWeek day=Helper.getEnumItem(DayOfWeek.class, 7);
Number no=Helper.getEnumItem(Number.class, 2);

如果属性有不同的类型,您可以使接口通用:

interface TypeWithIntProperty<T> {
  T getProperty();
}
enum Number implements TypeWithIntProperty<String> {
  ONE, TWO, THREE;

  public String getProperty() {
    return name().toLowerCase();
  }
}
enum DayOfWeek implements TypeWithIntProperty<Integer> {
  Monday(1), Tuesday(2), Wednesday(3), Thrusday(4), Friday(5), Saturday(6), Sunday(7);

  private final int value;

  DayOfWeek(int value) {
    this.value=value;
  }
  public Integer getProperty() {
    return value;
  }
}

public class Helper {
  public static <E extends Enum<E>&TypeWithIntProperty<P>,P>
                  E getEnumItem(Class<E> type, P value) {
    for(E constant: type.getEnumConstants())
      if(value.equals(constant.getProperty()))
        return constant;
    throw new IllegalArgumentException("no constant with "+value+" in "+type);
  }
}

DayOfWeek day=Helper.getEnumItem(DayOfWeek.class, 7);
Number no=Helper.getEnumItem(Number.class, "two");

更简洁但更冗长(在 Java 6 下)的替代方法是将 属性 抽象与具有 属性:

的类型分开
interface Property<T,V> {
  V get(T owner);
}
enum Number {
  ONE, TWO, THREE;
  static final Property<Number,String> NAME=new Property<Number,String>() {
    public String get(Number owner) { return owner.getName(); }
  };

  public String getName() {
    return name().toLowerCase();
  }
}
enum DayOfWeek {
  Monday(1), Tuesday(2), Wednesday(3), Thrusday(4), Friday(5), Saturday(6), Sunday(7);
  static final Property<DayOfWeek,Integer> INDEX=new Property<DayOfWeek,Integer>() {
    public Integer get(DayOfWeek owner) { return owner.getIndex(); }
  };

  private final int index;

  DayOfWeek(int value) {
    this.index=value;
  }
  public int getIndex() {
    return index;
  }
}
public class Helper {
  public static <E extends Enum<E>,P>
                  E getEnumItem(Class<E> type, Property<E,P> prop, P value) {
    for(E constant: type.getEnumConstants())
      if(value.equals(prop.get(constant)))
        return constant;
    throw new IllegalArgumentException("no constant with "+value+" in "+type);
  }
}

DayOfWeek day=Helper.getEnumItem(DayOfWeek.class, DayOfWeek.INDEX, 7);
Number no=Helper.getEnumItem(Number.class, Number.NAME, "two");

这在 Java 8 中会简单得多,您可以将 Property 实现为 DayOfWeek::getIndexNumber::getName 而不是内部 classes,在另一方面,由于我们没有从 Java 6 中的单一方法接口中获益,我们可以通过使用可以提供功能的抽象基 class 将其转化为优势,现在甚至可以使用缓存:

abstract class Property<T extends Enum<T>,V> {
  final Class<T> type;
  final Map<V,T> map;
  Property(Class<T> type) {
    this.type=type;
    map=new HashMap<V, T>();
    for(T constant: type.getEnumConstants())
    {
      T old = map.put(get(constant), constant);
      if(old!=null)
        throw new IllegalStateException("values not unique: "+get(constant));
    }
  }
  abstract V get(T owner);
  T getConstant(V value) {
    T constant=map.get(value);
    if(constant==null)
      throw new IllegalArgumentException("no constant "+value+" in "+type);
    return constant;
  }
}
enum Number {
  ONE, TWO, THREE;
  static final Property<Number,String> NAME=new Property<Number,String>(Number.class) {
    public String get(Number owner) { return owner.getName(); }
  };

  public String getName() {
    return name().toLowerCase();
  }
}
enum DayOfWeek {
  Monday(1), Tuesday(2), Wednesday(3), Thrusday(4), Friday(5), Saturday(6), Sunday(7);
  static final Property<DayOfWeek,Integer> INDEX
               =new Property<DayOfWeek,Integer>(DayOfWeek.class) {
    public Integer get(DayOfWeek owner) { return owner.getIndex(); }
  };

  private final int index;

  DayOfWeek(int value) {
    this.index=value;
  }
  public int getIndex() {
    return index;
  }
}

DayOfWeek day=DayOfWeek.INDEX.getConstant(7);
Number no=Number.NAME.getConstant("two");

帮手class真的很有帮助。我们已经开始遇到坏工厂的问题,导致枚举依赖于顺序——这是一个完全的偏差——

现在我重构了所有枚举 classes 以便它们使用助手和单个工厂。但是我通过以下方式更改了它的签名:

public static <E extends Enum<E> & IEnumWithValue> E factory(final E[] iConstants, int iValue) throws IllegalArgumentException

在我的枚举中 class 我有一个定义如下的成员:

private static  final MyEnum[]  mValues = MyEnum.values();

这样,我就不必在参数中传递枚举类型,也不必多次调用 values()class.getEnumConstants()