Преимущества перечисления Java по сравнению со старым шаблоном Typesafe Enum?

В Java до JDK1.5 шаблон "Typesafe Enum" был обычным способом реализации типа, который может принимать только конечное число значений:

public class Suit {
    private final String name;

    public static final Suit CLUBS =new Suit("clubs");
    public static final Suit DIAMONDS =new Suit("diamonds");
    public static final Suit HEARTS =new Suit("hearts");
    public static final Suit SPADES =new Suit("spades");    

    private Suit(String name){
        this.name =name;
    }
    public String toString(){
        return name;
    }
}

(см., например, совет 21 из статьи Блоха Эффективная Java).

Теперь в JDK1.5 + "официальный" способ, очевидно, использовать enum:

public enum Suit {
  CLUBS("clubs"), DIAMONDS("diamonds"), HEARTS("hearts"), SPADES("spades");

  private final String name;

  private Suit(String name) {
    this.name = name;
  }
}

Очевидно, что синтаксис немного лучше и лаконичнее (нет необходимости явно определять поля для значений, подходящие toString() предоставлены), но пока enum очень похож на шаблон Typesafe Enum.

Другие известные мне отличия:

  • перечисления автоматически предоставляют метод values()
  • перечисления могут использоваться в switch() (и компилятор даже проверяет, не забыли ли вы значение)

Но все это выглядит не более чем синтаксическим сахаром, даже с некоторыми добавленными ограничениями (например, enum всегда наследуется от java.lang.Enum и не может быть разделен на подклассы).

Есть ли другие, более фундаментальные преимущества, которые дает enum, которые нельзя реализовать с помощью шаблона Typesafe Enum?


person sleske    schedule 23.02.2011    source источник
comment
реальное преимущество: сериализация быстрее. Остальное можно подражать.   -  person bestsss    schedule 26.02.2011
comment
@sleske, что вы имеете в виду, когда компилятор проверяет переключатель. Я не слышал об этой функции. Лучше не использовать переключатель, а реализовать конкретный метод для каждого значения перечисления для абстрактного метода.   -  person human.js    schedule 19.06.2012
comment
@ user1198898: компилятор покажет ошибку, если вы повторите case в своем переключателе, и он может предупредить, если не все значения перечисления охвачены случаями. И да, часто вместо переключателя лучше использовать полиморфизм, но это зависит от обстоятельств.   -  person sleske    schedule 19.06.2012
comment
@sleske, спасибо, что разобрались.   -  person human.js    schedule 22.06.2012


Ответы (7)


  • «не может быть разделено на подклассы» не является ограничением. Это одно из больших преимуществ: он гарантирует, что всегда будет только тот набор значений, который определен в enum, и не более!
  • enum правильно обрабатывает сериализацию. Вы можете сделать это и с типобезопасными перечислениями, но об этом часто забывают (или просто не знают). Это гарантирует, что e1.equals(e2) всегда подразумевает e1 == e2 для любых двух enum значений e1 и e2 (и наоборот, что, вероятно, более важно).
  • Существуют определенные облегченные структуры данных, которые обрабатывают перечисления: EnumSet и EnumMap (украдены из этого ответа )
person Joachim Sauer    schedule 23.02.2011
comment
Я не думаю, что вы можете создать подкласс класса только с частным конструктором ... кроме вложенных в сам класс. - person Mark Peters; 23.02.2011
comment
@Mark: верно, но вы можете изменить класс, чтобы вместо него был конструктор protected. - person Joachim Sauer; 23.02.2011
comment
Да, я бы подумал, что конструктор должен быть закрытым, а класс окончательным. - person Mark Peters; 23.02.2011
comment
+1 сериализация. Это также настолько жестко, насколько вы можете получить против добавления значений в перечисление во время выполнения. Я вроде как хочу, чтобы каждое значение перечисления было последним подклассом, но я думаю, у вас не может быть обоих (не так ли?) - person GlenPeterson; 20.01.2017

Конечно, есть много преимуществ, которые другие люди упомянут здесь в качестве ответов. Самое главное, вы можете писать enums очень быстро, и они делают много вещей, например, реализуют Serializable, Comparable, equals(), toString(), hashCode() и т. Д., Которые вы не включили в свое перечисление.

Но я могу показать вам серьезный недостаток enum (IMO). Вы не только не можете наследовать их по своему желанию, но и не можете снабдить их универсальным параметром. Когда можно было написать это:

// A model class for SQL data types and their mapping to Java types
public class 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 {
        // [...]
    }
}

class Utility {

    // Enums equipped with generic types...
    public static <T> T doStuff(DataType<T> type) {
        return ...
    }
}

Это невозможно с перечислением:

// This can't be done
public enum DataType<T> {

    // Neither can this...
    INT<Integer>("int", Integer.class), 
    INT4<Integer>("int4", Integer.class), 

    // [...]
}
person Lukas Eder    schedule 23.02.2011
comment
Я понимаю, что вы имеете в виду, но не думаю, что это хороший пример. Зачем мне Club как отдельный тип от Diamond. На мой взгляд, это явно разные значения одного и того же типа, а не отдельные типы. - person Joachim Sauer; 23.02.2011
comment
Ты прав. Этот пример на самом деле просто пример. Попробую отобразить реальную проблему .. - person Lukas Eder; 23.02.2011
comment
Еще одна придирка: чтобы сделать ваш образец действительно типизированным, я бы оставил parse() абстрактным в DataType и реализовал его в анонимных внутренних классах (по одному на значение). - person Joachim Sauer; 23.02.2011
comment
Спасибо за редактирование, Иоахим. В то же время я заметил, что написал мусор :-) Насчет abstract: Это не обязательно улучшение. Все 3 DataType<Integer> реализации, вероятно, будут использовать один и тот же parse(String) код ... Так что никакого выигрыша здесь нет. Но опять же, это всего лишь пример ... - person Lukas Eder; 23.02.2011
comment
Вы можете создать подкласс перечисления, это просто должно произойти как анонимный тип. Все остальное нарушит предположения, сделанные в спецификации. - person josefx; 01.03.2011
comment
@josefx, можете ли вы привести пример? Как можно создать подкласс от перечисления? Enum не может быть разделен на подклассы, компилятор предотвращает это. И конкретный enum тип также не может быть разделен на подклассы, потому что все конструкторы в enum должны быть частными ... - person Lukas Eder; 02.03.2011
comment
@Lukas Eder enum AnEnum {ПРИВЕТ () {public void print () {System.out.println (Hello);}}, WORLD () {public void print () {System.out.println (World);}}; public abstract void print ();} ____________ И значения HELLO, и WORLD реализованы как подклассы AnEnum. - person josefx; 02.03.2011
comment
@josefx, интересно. Я не думал об этом. - person Lukas Eder; 03.03.2011
comment
+1 Shame enum нельзя использовать таким образом. Именно то, что мне нужно ... Спасибо! - person mothmonsterman; 12.02.2013

Теперь в JDK1.5 + "официальный" способ, очевидно, использовать enum:

public enum Suit {
  CLUBS("clubs"), DIAMONDS("diamonds"), HEARTS("hearts"), SPADES("spades");

  private final String name;

  private Suit(String name) {
    this.name = name;
  }
}

На самом деле это больше похоже на

 public enum Suit {
     CLUBS, DIAMONDS, HEARTS, SPADES;
 }

потому что перечисления уже предоставляют name() метод. Кроме того, они предоставляют метод ordinal() (который позволяет использовать эффективные структуры данных, такие как EnumSet и EnumMap), реализуют Serializable, переопределяют toString, предоставляют values() и valueOf(String name). Их можно использовать в операторе переключения, безопасном для типов, и они являются одиночными.

person meriton    schedule 26.02.2011

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

Java enums решает проблему с сериализацией / десериализацией. Гарантируется, что enum уникальность, и вы можете сравнить их с оператором ==.

Прочтите соответствующие главы в Effective Java 2nd Edition (об использовании перечислений вместо синглетонов, об использовании EnumSets и т. Д.).

person Roman    schedule 23.02.2011
comment
использовать перечисления вместо одиночных? каждый элемент перечисления является синглом !! - person Sean Patrick Floyd; 23.02.2011
comment
@ Шон Патрик Флойд: Да, это так, но это нечто большее (и в этом суть). - person sleske; 23.02.2011

EnumSet и _ 2_ - это настраиваемые структуры данных, построенные на основе определенных функций перечислений. У них есть удобные дополнительные функции, и они очень быстрые. Для них нет эквивалента (по крайней мере, с такой же элегантностью использования, см. Комментарии) без перечислений.

person Sean Patrick Floyd    schedule 23.02.2011
comment
BitSet - очень эффективная замена для EnumSet если и только если вы замените свой enum значениями int вместо безопасной реализации шаблона перечисления. - person Joachim Sauer; 23.02.2011
comment
@Joachim предоставлен. но API намного уродливее, и EnumSet имеет отличные фабричные методы: copyOf(), complementOf() и т. д. - person Sean Patrick Floyd; 23.02.2011
comment
Я согласен, что EnumSet намного лучше. Я просто не согласен с тем, что эквивалента нет. - person Joachim Sauer; 23.02.2011
comment
Вы можете просто добавить метод ordinal() в свой класс typeafe enum и реализовать большую часть остального на его основе. Но конечно, enum дает более удобный способ написать это, и вся инфраструктура уже есть. - person Paŭlo Ebermann; 23.02.2011

Кроме того:

Перечисления JDK5 можно легко использовать в операторах switch-case с хорошей поддержкой IDE.

Suit suit = ...; 
switch (suit) { 
    case SPADES: System.out.println("Motorhead!"); break;
    default: System.out.println("Boring ..");
}
person Heiko Rupp    schedule 23.02.2011

Сам по себе синтаксический сахар стоит того :-P В конце концов, это тоже для (:).

А если серьезно, тот факт, что автоматически используются name () и ordinal () из коробки, для их перечисления, использования в switch (), для присоединения к ним дополнительных значений, является для них хорошими аргументами: это позволяет избежать большого количества шаблонного кода. .

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

person PhiLho    schedule 24.02.2011
comment
На самом деле, я считаю, что перечисления достаточно легкие. В частности, если вы действительно используете их в количестве, где это важно, вы можете использовать EnumSet и EnumMap, которые являются легковесными. - person sleske; 24.02.2011