Java перечисления и дженерики - PullRequest
13 голосов
/ 24 февраля 2011

Эта штука беспокоит меня некоторое время. Я задавал вопросы и раньше, но, вероятно, с плохой формулировкой и примером, который был слишком абстрактным. Так что не было ясно, о чем я на самом деле спрашивал. Я попробую еще раз. И, пожалуйста, не спешите с выводами. Я ожидаю, что на этот вопрос нелегко ответить!

почему я не могу иметь перечисление с параметрами универсального типа в 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, а дженерики - в приведение и синтетические методы.

но почему компилятор не может идти дальше и допускать общие перечисления ??

EDIT : Это то, что я ожидал бы как сгенерированный компилятором Java-код:

public class DataType<T> extends Enum<DataType<?>> {
    // [...]
}

Ответы [ 5 ]

4 голосов
/ 24 февраля 2011

Я собираюсь немного угадать и сказать, что это из-за проблем с ковариацией для параметра типа самого класса Enum, который определен как Enum<E extends Enum<E>>, хотя это немного много, чтобы исследовать все угловые случаи этого.

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

Я знаю, что всегда испытываю боль, когда пытаюсь придумать это с помощью Generics, и я представляю, как дизайнеры языка заглянули в эту пропасть и решили не идти туда, тем более что функции разрабатывались одновременно, что будет означать еще большую неопределенность для стороны Enum вещей.

Или, другими словами, у него были бы все проблемы Class<T> при работе с классами, которые сами имеют общие параметры, и вам пришлось бы выполнять много приведения и иметь дело с необработанными типами. Не совсем то, что чувствовали языковые дизайнеры, стоило того для рассматриваемого вами варианта использования.

РЕДАКТИРОВАТЬ: В ответ на комментарии (а Том - отрицательное мнение?), Вложенный универсальный параметр делает все виды плохих вещей. Enum реализует Comparable. Это просто не сработало бы для сравнения двух произвольных элементов перечисления в клиентском коде, если бы генерики были в игре. Как только вы работаете с параметром Generic параметра Generic, вы сталкиваетесь с различными проблемами и головными болями. Трудно разработать класс, который справится с этим хорошо. В случае сопоставимого я не мог найти способ заставить его сравнить два произвольных члена перечисления, не возвращаясь к необработанным типам и не получая предупреждение компилятора. Не могли бы вы?

На самом деле вышеприведенное неверно, так как я использовал DataType в вопросе в качестве шаблона для размышлений об этом, но на самом деле Enum имел бы подкласс, так что это не совсем правильно.

Однако я поддерживаю суть моего ответа. Том поднял EnumSet.complementOf и, конечно, у нас все еще есть valueOf, который создает проблемы, и в той степени, в которой дизайн Enum мог бы сработать, мы должны понимать, что это 20/20 задним числом. Enum разрабатывался одновременно с генериками и не имел возможности проверять все подобные случаи. Особенно учитывая, что сценарий использования Enum с универсальным параметром довольно ограничен. (Но опять же, как и в случае использования EnumSet).

1 голос
/ 25 февраля 2011

Я не думаю, что невозможно генерировать enum.Если бы вы могли взломать компилятор, у вас мог бы быть подкласс Enum, который является универсальным, и файл класса вашего универсального enum не вызовет проблем.

Но, в конце концов, enum в значительной степени является синтаксическим сахаром,В C, C ++, C # перечисления в основном являются псевдонимами для констант int.Java дает ему больше возможностей, но все равно предполагается для представления простых элементов.

Где-то люди должны провести черту.Тот факт, что у класса есть перечислимые экземпляры, не означает, что он должен быть перечислением.Если он достаточно сложен в других областях, он заслуживает того, чтобы быть обычным классом.

В вашем случае не так уж и много преимуществ сделать DataType перечислением.Вы можете использовать enum в switch-case, вот и все, большое дело.Не перечисленная версия DataType работает просто отлично.

0 голосов
/ 25 февраля 2011
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 для метода разбора во время выполнения.

Перечисления в основном статические, они существуют только один раз.И нет смысла применять универсальную типизацию к статическим типам.

Надеюсь, это поможет.

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

0 голосов
/ 24 февраля 2011

Я думаю, что причина, по которой вы хотите параметризовать перечисление с помощью <T>, сводится к возможности иметь разные сигнатуры метода для различных констант перечисления.

В вашем примере подпись (тип параметров и тип возвращаемого значения) для parse будет:

  • для Datatype.INT: int parse(String)
  • для Datatype.VARCHAR: String parse(String)
  • и т. Д.

Так как же компилятор сможет проверять что-то вроде:

Datatype type = ...
...
int x = type.parse("45");

???

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

0 голосов
/ 24 февраля 2011

Вот как я об этом думаю -

У регулярных классов есть экземпляры.Вы создаете новый экземпляр класса, используете его для каких-то целей, а затем удаляете его.Например, List<String> - это список строк.Я могу делать все, что захочу, со строками, а затем, когда я закончу, я смогу позже сделать то же самое с целыми числами.

Для меня перечислители не являются типами, экземпляры которых вы создаете.Это то же самое, что и синглтон.Итак, я понимаю, почему JAVA не разрешает генерики для Enums, потому что вы действительно не можете создать новый экземпляр типа Enum для использования временного, как вы делаете с классами.Перечисления должны быть статическими и иметь только один экземпляр в глобальном масштабе.На мой взгляд, не имеет смысла разрешать генерики для класса, который имеет только один экземпляр в глобальном масштабе.

Надеюсь, это поможет.

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