Можно ли использовать == в перечислениях в Java?

111

Можно ли использовать ==перечисления в Java или мне нужно использовать .equals()? В моем тестировании ==всегда работает, но я не уверен, что мне это гарантировано. В частности, .clone()в перечислении нет метода, поэтому я не знаю, можно ли получить перечисление, для которого .equals()возвращалось бы другое значение, чем ==.

Например, это нормально:

public int round(RoundingMode roundingMode) {
  if(roundingMode == RoundingMode.HALF_UP) {
    //do something
  } else if (roundingMode == RoundingMode.HALF_EVEN) {
    //do something
  }
  //etc
}

Или мне нужно написать так:

public int round(RoundingMode roundingMode) {
  if(roundingMode.equals(RoundingMode.HALF_UP)) {
    //do something
  } else if (roundingMode.equals(RoundingMode.HALF_EVEN)) {
    //do something
  }
  //etc
}
койка
источник
@assylias, этот вопрос был первым. Возможно, это флаг для ♦ внимания, поскольку я не совсем уверен, следует ли их объединить.
Мэтт Болл,
@MattBall Я думаю, что ответ на ваш вопрос, который цитирует JLS, - лучший ответ, поэтому я решил закрыть этот.
assylias 03

Ответы:

149

Только мои 2 цента: вот код Enum.java, опубликованный Sun и являющийся частью JDK:

public abstract class Enum<E extends Enum<E>>
    implements Comparable<E>, Serializable {

    // [...]

    /**
     * Returns true if the specified object is equal to this
     * enum constant.
     *
     * @param other the object to be compared for equality with this object.
     * @return  true if the specified object is equal to this
     *          enum constant.
     */
    public final boolean equals(Object other) { 
        return this==other;
    }


}
Varkhan
источник
4
Спасибо! Думаю, если бы я просто подумал зайти в .equals () с компилятором, я бы увидел это ...
Кип
77

Да, == в порядке - для каждого значения гарантированно будет только одна ссылка.

Однако есть лучший способ написать свой круглый метод:

public int round(RoundingMode roundingMode) {
  switch (roundingMode) {
    case HALF_UP:
       //do something
       break;
    case HALF_EVEN:
       //do something
       break;
    // etc
  }
}

Еще лучший способ сделать это - поместить функциональность в само перечисление, чтобы вы могли просто вызвать roundingMode.round(someValue). Это затрагивает суть перечислений Java - они объектно-ориентированные перечисления, в отличие от «именованных значений», которые можно найти где-либо еще.

РЕДАКТИРОВАТЬ: спецификация не очень ясна, но в разделе 8.9 говорится:

Тело перечисляемого типа может содержать константы перечисления. Константа перечисления определяет экземпляр типа перечисления. Тип перечисления не имеет экземпляров, кроме тех, которые определены его константами перечисления.

Джон Скит
источник
Я бы хотел поверить вам на слово, но если бы вы могли предоставить ссылку на какую-то официальную документацию, было бы лучше ...
Кип
переключатель бесполезен, когда между разными случаями много совпадений. Кроме того, RoundingMode является частью java.math, поэтому я не могу добавить к нему метод.
Кип,
2
О ... и вы сомневаетесь в Джоне Ските? Вы здесь не так давно;)
Джоэл Кохорн
перечисления в операторах switch? Не знал, что это возможно. Однажды мне придется попробовать.
luiscubal,
Инкапсуляция логики в перечислениях с использованием абстрактных методов - это реальная сила перечислений. Это делает ваш код более надежным; когда вы добавляете новое значение перечисления в будущем, компилятор заставит вас реализовать соответствующую логику, вам не нужно помнить о добавлении case к нескольким операторам switch.
Эндрю Свон,
13

Да, это как если бы вы создали экземпляры singleton для каждого значения в перечислении:

общедоступный абстрактный класс RoundingMode {
  общедоступный статический финальный RoundingMode HALF_UP = new RoundingMode ();
  общедоступный статический финальный RoundingMode HALF_EVEN = new RoundingMode ();

  private RoundingMode () {
    // частная область предотвращает любые подтипы вне этого класса
  }
}

Тем не менее , то enumконструкция дает различные преимущества:

  • ToString () каждого экземпляра печатает имя, указанное в коде.
  • (Как упоминалось в другом сообщении) переменную типа enum можно сравнить с константами с помощью switch-caseструктуры управления.
  • Все значения в перечислении могут быть запрошены с использованием valuesполя, которое «сгенерировано» для каждого типа перечисления.
  • Вот главный вопрос сравнений идентичности: значения перечисления выдерживают сериализацию без клонирования.

Сериализация - большая проблема. Если бы я использовал приведенный выше код вместо перечисления, вот как ведет себя равенство идентичности:

RoundingMode original = RoundingMode.HALF_UP;
assert (RoundingMode.HALF_UP == оригинал); // проходит

ByteArrayOutputStream baos = новый ByteArrayOutputStream ();
ObjectOutputStream oos = новый ObjectOutputStream (baos);
oos.writeObject (оригинал);
oos.flush ();

ByteArrayInputStream bais = новый ByteArrayInputStream (baos.toByteArray ());
ObjectInputStream ois = новый ObjectInputStream (bais);
RoundingMode deserialized = (RoundingMode) ois.readObject ();

assert (RoundingMode.HALF_UP == десериализовано); // не удается
assert (RoundingMode.HALF_EVEN == десериализовано); // не удается

Вы можете решить эту проблему без перечисления, используя технику, которая включает writeReplaceи readResolve(см. Http://java.sun.com/j2se/1.4.2/docs/api/java/io/Serializable.html ) ...

Думаю, дело в том, что Java делает все возможное, чтобы позволить вам использовать идентификаторы значений перечисления для проверки равенства; это рекомендуемая практика.

Дилум Ранатунга
источник
1
Исправлена ​​ошибка сериализации. bugs.sun.com/bugdatabase/view_bug.do?bug_id=6277781
Дэвид И.
@DavidI. Спасибо за обновление. Это очень тревожная ошибка, и это полезно знать!
Dilum Ranatunga
1
@DilumRanatunga Сначала я думал, что это повлияет на меня, но, похоже, они работают нормально после передачи их через соединение RMI.
David I.
11

== сравнивает ссылки двух объектов. Для перечислений гарантируется, что будет только один экземпляр, и поэтому для любых двух одинаковых перечислений == будет истинным.

Ссылка:

http://www.ajaxonomy.com/2007/java/making-the-most-of-java-50-enum-tricks

(ничего не нашел в документации Sun)

леванд
источник
6

Вот какой-то злой код, который может вас заинтересовать. : D

public enum YesNo {YES, NO}

public static void main(String... args) throws Exception {
    Field field = Unsafe.class.getDeclaredField("theUnsafe");
    field.setAccessible(true);
    Unsafe unsafe = (Unsafe) field.get(null);
    YesNo yesNo = (YesNo) unsafe.allocateInstance(YesNo.class);

    Field name = Enum.class.getDeclaredField("name");
    name.setAccessible(true);
    name.set(yesNo, "YES");

    Field ordinal = Enum.class.getDeclaredField("ordinal");
    ordinal.setAccessible(true);
    ordinal.set(yesNo, 0);

    System.out.println("yesNo " + yesNo);
    System.out.println("YesNo.YES.name().equals(yesNo.name()) "+YesNo.YES.name().equals(yesNo.name()));
    System.out.println("YesNo.YES.ordinal() == yesNo.ordinal() "+(YesNo.YES.ordinal() == yesNo.ordinal()));
    System.out.println("YesNo.YES.equals(yesNo) "+YesNo.YES.equals(yesNo));
    System.out.println("YesNo.YES == yesNo " + (YesNo.YES == yesNo));
}
Питер Лоури
источник
1
@Peter, не могли бы вы включить импорт этого кода? Каунда не найти Unsafe.class.
rumman0786
3

Перечисления - отличное место для застревания полиморфного кода.

enum Rounding {
  ROUND_UP {
    public int round(double n) { ...; }
  },
  ROUND_DOWN {
    public int round(double n) { ...; }
  };

  public abstract int round(double n);
}

int foo(Rounding roundMethod) {
  return roundMethod.round(someCalculation());
}

int bar() {
  return foo(Rounding.ROUND_UP);
}
Полмуррей
источник
1
Да, но у меня нет java.math.RoundingMode, поэтому я не могу этого сделать в моем случае.
Кип,