Перечисления Java: реализация общих методов, избегание дублирования

Допустим, у меня есть два разных перечисления

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 и пока не можем обновиться. Любая помощь приветствуется. Благодарю.


person Zangdar    schedule 22.08.2016    source источник
comment
Перечисление не может расширять ни класс, ни другое перечисление, поэтому либо вы пишете метод один раз для каждого (и в идеале делаете перечисление реализующим интерфейс, который определяет метод), либо вы пишете код вне перечислений.   -  person Aaron    schedule 22.08.2016


Ответы (5)


Ваш пример кода немного вводит в заблуждение, поскольку он возвращает константу с тем же порядковым номером, а не с тем же значением свойства. Чтобы абстрагировать поиск константы со значением свойства, вы должны абстрагировать свойство, например.

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::getIndex или Number::getName вместо внутренних классов, с другой стороны, поскольку мы не получаем преимущества от интерфейса с одним методом в Java 6, мы можем превратить это в преимущество. с помощью абстрактного базового класса, который может обеспечить функциональность, теперь даже с кэшированием:

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");
person Holger    schedule 22.08.2016
comment
Я использую вспомогательный класс, и он работает очень хорошо. Здесь я могу централизовать код для нескольких перечислений. В целом, на мой взгляд, код все еще беспорядочный, но я думаю, что это лучшее, что я могу здесь сделать. Вопрос, что означает & в ‹E extends Enum‹E›&TypeWithIntProperty›? Почему не запятая? - person Zangdar; 01.09.2016
comment
Он называется типом пересечения. Тип E должен быть enum (расширить Enum<E>) и он должен реализовывать TypeWithIntProperty того же типа. Запятая предназначена для объявления нескольких параметров типа, но это один параметр типа с несколькими ограничениями. - person Holger; 01.09.2016
comment
Большое спасибо за эту точность! - person Zangdar; 01.09.2016

Вы можете обернуть свои реализации в общий вспомогательный класс и использовать его во всех своих реализациях. К сожалению, вам придется копировать вызовы в помощник; Методы 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);}
}

Демонстрация

person Sergey Kalinichenko    schedule 22.08.2016

Ты можешь сделать это:

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

EDITED: Это похоже на идею @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));
}
person David Pérez Cabrera    schedule 22.08.2016
comment
В этом случае это будет работать, однако у меня будут некоторые методы, которые зависят от того, что на самом деле представляет собой перечисление. Таким образом, хотя ваш getEnumItem будет работать для любого перечисления, другой метод, который вернет целое число или строку на основе члена, содержащегося в перечислении, не будет. Это может быть решением, но оно не будет таким безопасным, как хотелось бы. - person Zangdar; 22.08.2016

Вы можете использовать интерфейс:

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 нет методов по умолчанию. Кроме того, это имеет несколько неудобное / невыгодное использование, поскольку это будет метод экземпляра (хотя вы можете сделать его статическим, если хотите).

Для более ранних версий вам нужен способ предоставить тип класса, поскольку на самом деле нет другого способа предоставить его для перечисления, в котором вы использовали бы ответ Дэвида выше.

person Rogue    schedule 22.08.2016
comment
ОП сказал: Мы используем Java 1.6 и пока не можем обновиться. - person David Pérez Cabrera; 22.08.2016
comment
Ну, рип. В этом тоже есть недостаток, поскольку он нестатичен. Собираюсь улучшить его, просто хотел пока отложить эту идею как вариант. - person Rogue; 22.08.2016
comment
Спасибо за это, однако я также должен добавить, что я не могу избавиться от члена перечисления, которое в моем примере является целым числом, но в моем коде на самом деле это перечисление состояния или строка. - person Zangdar; 22.08.2016

Класс Helper действительно полезен. У нас уже начались проблемы с плохими фабриками, из-за чего перечисления зависели от порядка, что является полным отклонением от нормы.

Теперь я реорганизовал все классы перечислений, чтобы они использовали помощника и одну фабрику. Однако я изменил его подпись следующим образом:

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

В моем классе enum у меня есть член, определенный следующим образом:

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

Таким образом, мне не нужно передавать тип перечисления в аргументе, и мне не нужно умножать вызовы values() или class.getEnumConstants()

person Zangdar    schedule 06.09.2016