Почему не нужно, чтобы литералы перечисления Java могли иметь параметры типового типа?

Переменные Java отлично. Так что дженерики. Конечно, мы все знаем ограничения последнего из-за стирания стилей. Но есть одна вещь, которую я не понимаю, почему я не могу создать перечисление вроде этого:

public enum MyEnum<T> {
    LITERAL1<String>,
    LITERAL2<Integer>,
    LITERAL3<Object>;
}

Этот общий параметр типа <T>, в свою очередь, может быть полезен в разных местах. Представьте себе общий тип параметра для метода:

public <T> T getValue(MyEnum<T> param);

Или даже в самом классе перечисления:

public T convert(Object o);

Более конкретный пример # 1

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

  • Перечисляет, потому что тогда я могу перечислить конечный набор ключей свойств
  • Generics, потому что тогда у меня может быть тип безопасности типа для хранения свойств
public interface MyProperties {
     public <T> void put(MyEnum<T> key, T value);
     public <T> T get(MyEnum<T> key);
}

Более конкретный пример # 2

У меня есть перечисление типов данных:

public interface DataType<T> {}

public enum SQLDataType<T> implements DataType<T> {
    TINYINT<Byte>,
    SMALLINT<Short>,
    INT<Integer>,
    BIGINT<Long>,
    CLOB<String>,
    VARCHAR<String>,
    ...
}

Каждый литерал перечисления, очевидно, будет иметь дополнительные свойства, основанные на родовом типе <T>, и в то же время быть перечислением (неизменяемым, одиночным, перечислимым и т.д. и т.д.).

Вопрос:

Не думал ли об этом? Является ли это ограничение, связанное с компилятором? Учитывая тот факт, что ключевое слово " перечисление" реализовано как синтаксический сахар, представляющий сгенерированный код для JVM, я не понимаю этого ограничения.

Кто может это объяснить? Прежде чем ответить, рассмотрите следующее:

  • Я знаю, что общие типы стираются: -)
  • Я знаю, что есть обходные пути с использованием объектов класса. Это обходные пути.
  • Общие типы приводят к генерации типов, генерируемых компилятором, где это применимо (например, при вызове метода convert()
  • Общий тип <T> будет на перечислении. Следовательно, он связан каждым из литералов перечисления. Следовательно, компилятор знал бы, какой тип применять при написании чего-то вроде String string = LITERAL1.convert(myObject); Integer integer = LITERAL2.convert(myObject);
  • То же самое относится к параметру generic type в методе T getvalue(). Компилятор может применять приведение типов при вызове String string = someClass.getValue(LITERAL1)

Ответ 1

Это сейчас обсуждается как Расширенные перечисления JEP-301. Пример, приведенный в JEP, является именно тем, что я искал:

enum Argument<X> { // declares generic enum
   STRING<String>(String.class), 
   INTEGER<Integer>(Integer.class), ... ;

   Class<X> clazz;

   Argument(Class<X> clazz) { this.clazz = clazz; }

   Class<X> getClazz() { return clazz; }
}

Class<String> cs = Argument.STRING.getClazz(); //uses sharper typing of enum constant

Ответ 2

Ответ на вопрос:

из-за стирания типа

Ни один из этих двух методов невозможен, поскольку тип аргумента стирается.

public <T> T getValue(MyEnum<T> param);
public T convert(Object);

Чтобы реализовать эти методы, вы, однако, могли бы создать свой перечисление как:

public enum MyEnum {
    LITERAL1(String.class),
    LITERAL2(Integer.class),
    LITERAL3(Object.class);

    private Class<?> clazz;

    private MyEnum(Class<?> clazz) {
      this.clazz = clazz;
    }

    ...

}

Ответ 3

Потому что вы не можете. Шутки в сторону. Это можно добавить к спецификации языка. Этого не было. Это добавит некоторую сложность. Это преимущество для стоимости означает, что это не высокий приоритет.

Обновление: в настоящее время добавляется на язык под JEP 301: Расширенные перечисления.

Ответ 4

В ENUM существуют другие методы, которые бы не сработали. Что вернет MyEnum.values()?

Как насчет MyEnum.valueOf(String name)?

Для valueOf, если вы думаете, что компилятор может сделать общий метод, например

public static MyEnum valueOf (имя строки);

чтобы вызвать его как MyEnum<String> myStringEnum = MyEnum.value("some string property"), это тоже не сработает. Например, что, если вы вызываете MyEnum<Int> myIntEnum = MyEnum.<Int>value("some string property")? Невозможно реализовать этот метод для правильной работы, например, для исключения исключения или возврата null, когда вы вызываете его как MyEnum.<Int>value("some double property") из-за стирания типа.

Ответ 5

Откровенно говоря, это похоже на большее решение в поисках проблемы, чем что-либо.

Вся цель java перечисления состоит в том, чтобы моделировать перечисление экземпляров типа, которые разделяют сходные свойства таким образом, чтобы обеспечить согласованность и богатство, превышающее сопоставимые представления String или Integer.

Возьмем пример перечисления текстовой книги. Это не очень полезно или непротиворечиво:

public enum Planet<T>{
    Earth<Planet>,
    Venus<String>,
    Mars<Long>
    ...etc.
}

Почему я хочу, чтобы у моих разных планет были разные преобразования общего типа? Какую проблему он решает? Оправдывает ли это усложнение семантики языка? Если мне это нужно, это перечисление - лучший инструмент для его достижения?

Дополнительно, как бы вы могли управлять сложными преобразованиями?

для экземпляра

public enum BadIdea<T>{
   INSTANCE1<Long>,
   INSTANCE2<MyComplexClass>;
}

Его достаточно легко с String Integer, чтобы указать имя или порядковый номер. Но дженерики позволят вам поставлять любой тип. Как бы вы справились с преобразованием в MyComplexClass? Теперь вы удаляете две конструкции, заставляя компилятор знать, что существует ограниченное подмножество типов, которое может быть предоставлено общим перечислениям и вводит дополнительную путаницу в концепцию (Generics), которая уже, похоже, ускользнула от многих программистов.

Ответ 6

Я думаю, потому что в основном Enums не может быть установлен

Где бы вы установили класс T, если JVM разрешил вам это сделать?

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

new MyEnum < > ()?

Тем не менее может быть полезным следующий подход

public enum MyEnum{

    LITERAL1("s"),
    LITERAL2("a"),
    LITERAL3(2);

    private Object o;

    private MyEnum(Object o) {
        this.o = o;
    }

    public Object getO() {
        return o;
    }

    public void setO(Object o) {
        this.o = o;
    }   
}

Ответ 7

Becasue "enum" - это аббревиатура для перечисления. Это просто набор именованных констант, которые стоят вместо порядковых номеров, чтобы сделать код более читаемым.

Я не вижу, что может означать предполагаемое значение константы, параметризованной по типу.