Почему не нужно, чтобы литералы перечисления 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" - это аббревиатура для перечисления.
Это просто набор именованных констант, которые стоят вместо порядковых номеров, чтобы сделать код более читаемым.
Я не вижу, что может означать предполагаемое значение константы, параметризованной по типу.