Переменные и дженерики Java
Эта вещь беспокоит меня какое-то время. Я уже просил questions, но, вероятно, с плохой формулировкой и примером, который был слишком абстрактным. Поэтому было непонятно, что я на самом деле спрашивал. Я попробую еще раз. И, пожалуйста, не подходите к выводам. Я ожидаю, что ответить на вопрос нелегко!
Почему я не могу иметь перечисление с параметрами общего типа в Java?
Вопрос не в том, почему это невозможно, синтаксически. Я знаю, что это просто не поддерживается. Вопрос в том, почему люди JSR "забывали" или "пропускали" эту очень полезную функцию? Я не могу представить себе причину, связанную с компилятором, почему это было бы невозможно.
Вот что я хотел бы сделать. Это возможно на Java. Это способ Java 1.4 для создания типов перечислений:
// A model class for SQL data types and their mapping to Java types
public class DataType<T> implements Serializable, Comparable<DataType<T>> {
private final String name;
private final Class<T> type;
public static final DataType<Integer> INT = new DataType<Integer>("int", Integer.class);
public static final DataType<Integer> INT4 = new DataType<Integer>("int4", Integer.class);
public static final DataType<Integer> INTEGER = new DataType<Integer>("integer", Integer.class);
public static final DataType<Long> BIGINT = new DataType<Long> ("bigint", Long.class);
private DataType(String name, Class<T> type) {
this.name = name;
this.type = type;
}
// Returns T. I find this often very useful!
public T parse(String string) throws Exception {
// [...]
}
// Check this out. Advanced generics:
public T[] parseArray(String string) throws Exception {
// [...]
}
// Even more advanced:
public DataType<T[]> getArrayType() {
// [...]
}
// [ ... more methods ... ]
}
И тогда вы можете использовать <T>
во многих других местах
public class Utility {
// Generic methods...
public static <T> T doStuff(DataType<T> type) {
// [...]
}
}
Но это невозможно с перечислением:
// This can't be done
public enum DataType<T> {
// Neither can this...
INT<Integer>("int", Integer.class),
INT4<Integer>("int4", Integer.class),
// [...]
}
Теперь, как я уже сказал. Я знаю, что эти вещи были спроектированы именно так. enum
- синтаксический сахар. Так что дженерики. На самом деле, компилятор выполняет всю работу и преобразовывает enums
в подклассы java.lang.Enum
и генерики в отливки и синтетические методы.
, но почему компилятор не может пойти дальше и разрешить общие перечисления?
ИЗМЕНИТЬ:
Это то, что я ожидал бы в виде кода Java, созданного компилятором:
public class DataType<T> extends Enum<DataType<?>> {
// [...]
}
Ответы
Ответ 1
Я немного угадаю и скажу, что это из-за ошибок ковариации в параметре типа самого класса Enum, который определяется как Enum<E extends Enum<E>>
, хотя немного изучать все угловые случаи из этого.
Кроме того, первичный вариант использования перечислений - это такие вещи, как EnumSet и valueOf, где у вас есть коллекция вещей с разными генерическими параметрами и получить значение из строки, все из которых не будут поддерживать или ухудшать общий параметр на само перечисление.
Я знаю, что я всегда в мире боли, когда я пытаюсь понять это с помощью Generics, и я думаю, что разработчики языка заглянули в эту бездну и решили не идти туда, тем более, что функции были разработаны одновременно, что будет означать еще большую неопределенность для стороны Enum вещей.
Или иначе, у него возникнут проблемы с Class<T>
при работе с классами, которые сами имеют общие параметры, и вам придется делать много кастингов и работать с необработанными типами. Не совсем то, что чувствовали разработчики языка, стоило того, на что вы смотрите.
EDIT: В ответ на комментарии (и Tom - downvote?), вложенный общий параметр делает всевозможные плохие вещи. Enum реализует Comparable. Это просто не помогло бы сравнить два произвольных элемента перечисления в клиентском коде, если бы дженерики играли. После того, как вы справитесь с параметром Generic параметра Generic, вы получите всевозможные проблемы и головные боли. Трудно создать класс, который хорошо его обрабатывает. В случае сопоставимого я не мог найти способ заставить его работать, чтобы сравнить двух произвольных членов перечисления без возврата к необработанным типам и получения предупреждения о компиляторе. Не могли бы вы?
На самом деле выше это смущающе неправильно, поскольку я использовал DataType в вопросе как мой шаблон для размышлений об этом, но на самом деле Enum будет иметь подкласс, так что это не совсем правильно.
Тем не менее, я согласен с сутью моего ответа. Том поднял EnumSet.complementOf
, и, конечно же, у нас все еще есть valueOf
, что создает проблемы, и в той степени, в которой дизайн Enum мог работать, мы должны понять, что это 20/20 задним числом. Enum разрабатывался одновременно с дженериками и не имел преимущества для проверки всех таких угловых случаев. Особенно учитывая, что прецедент для Enum с общим параметром весьма ограничен. (Но опять же, так же, как и для EnumSet).
Ответ 2
Я не думаю, что невозможно получить обобщенное перечисление. Если вы можете взломать компилятор, у вас может быть подкласс Enum, который является общим, а файл класса вашего общего перечисления не вызовет проблем.
Но в конце концов, перечисление в значительной степени является синтаксическим сахаром. В C, С++, С# перечисления представляют собой в основном псевдоним для int-констант. Java дает ему больше энергии, но он все еще должен представлять простые элементы.
Где-то люди должны рисовать линию. Просто потому, что класс имеет перечисленные экземпляры, не означает, что он должен быть перечислением. Если он достаточно изощрен в других областях, он заслуживает того, чтобы быть обычным классом.
В вашем случае нет большого преимущества, чтобы сделать DataType
перечисление. Вы можете использовать enum в switch-case, что об этом, большое дело. Не-enum verion DataType
работает просто отлично.
Ответ 3
Вот как я об этом думаю -
У обычных классов есть экземпляры. Вы создаете новый экземпляр класса, используя его для какой-либо цели, а затем удаляете его. Например, List<String>
- список строк. Я могу делать то, что когда-либо хочу делать со строками, а затем, когда я закончил, я могу позже реализовать ту же функциональность с целыми числами.
Мне перечислены не типы, которые вы создаете экземпляры. Это то же самое, что и синглтон. Поэтому я могу понять, почему JAVA не разрешает генерики для Enums, потому что вы действительно не можете создать новый экземпляр типа Enum для использования временного типа, как в случае с классами. Предполагается, что перечисления являются статическими и имеют только один экземпляр во всем мире. Для меня было бы бессмысленно разрешать общие для класса, которые имеют только один экземпляр во всем мире.
Надеюсь, это поможет.
Ответ 4
Я думаю, что причина, по которой вы хотите параметризовать enum с помощью <T>
, сводится к тому, чтобы иметь разные сигнатуры методов для различных констант перечисления.
В вашем примере подпись (тип параметров и тип возврата) для parse
будет выглядеть следующим образом:
- для
Datatype.INT
: int parse(String)
- для
Datatype.VARCHAR
: String parse(String)
- и т.д.
Итак, как бы компилятор смог проверить тип:
Datatype type = ...
...
int x = type.parse("45");
???
Чтобы применить статическую типизацию и typechecking к этому виду выражения, сигнатура метода должна быть одинаковой для всех экземпляров. Однако, в конце концов, вы предлагаете иметь разные сигнатуры методов для разных экземпляров... То почему это невозможно сделать в Java.
Ответ 5
public enum GenericEnum<T> {
SIMPLE, COMPLEX;
public T parse(String s) {
return T.parse(s);
}
}
public void doSomething() {
GenericEnum<Long> longGE = GenericEnum<Long>.SIMPLE;
GenericEnum<Integer> intGE = GenericEnum<Integer>.SIMPLE;
List<Long> longList = new LinkedList<Long>();
List<Integer> intList = new LinkedList<Integer>();
assert(longGE == intGE); // 16
assert(stringList.equals(intList)); // 17
Object x = longGE.parse("1"); // 19
}
Утверждения в строках 16 и 17 являются истинными. Общие типы недоступны во время выполнения.
Одно из преимуществ перечисления состоит в том, что вы можете использовать == для их сравнения. Утверждение в строке 16 будет равно true.
В строке 19 мы сталкиваемся с проблемой. longGE и intGE - это один и тот же объект (как показано в строке 16). Что будет получено в результате синтаксического анализа ( "1" )? Общая информация типа недоступна во время выполнения. Поэтому не было способа определить T для метода анализа во время выполнения.
Перечисления в основном статичны, они существуют только один раз. И не имеет смысла применять типичную типизацию к статическим типам.
Надеюсь, это поможет.
Примечание. Это не должно быть рабочим кодом. Он использует синтаксис, предложенный в исходном вопросе.