Чи добре використовувати == на enums на 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

Відповіді:


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;
    }


}

4
Дякую! Я думаю, якби я тільки думав перейти на .equals () із компілятором, я б це бачив ...
Kip

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 - вони об'єктно-орієнтовані перерахунки, на відміну від "названих значень", що зустрічаються в інших місцях.

EDIT: Специфікація не дуже чітка, але в розділі 8.9 зазначено:

Тіло enum типу може містити константи enum. Константа enum визначає екземпляр типу enum. Тип enum не має інших випадків, ніж ті, які визначені його константами enum.


Я хотів би прийняти ваше слово, але якщо ви можете надати посилання на якусь офіційну документацію, що буде краще ...
Kip

Перемикач не корисний, коли між різними випадками багато перекриттів. Також RoundingMode є частиною java.math, тому я не можу додати метод до нього.
Кіп

2
О - і ти сумніваєшся у Джона Скіта? Ви тут давно не бували;)
Джоель Коухорн

перераховує в операторах переключення? Не знав, що це можливо. Мені доведеться спробувати це одного дня.
luiscubal

Інкапсуляція логіки всередині переліків за допомогою абстрактних методів є реальною силою переліків. Це робить ваш код набагато надійнішим; коли ви додасте нове значення enum у майбутньому, компілятор змусить вас реалізувати відповідну логіку, вам не потрібно пам'ятати, щоб додати випадок до кількох операторів перемикання.
Ендрю Лебедь

13

Так, це так, ніби ви створили одиночні екземпляри для кожного значення в enum:

публічний абстрактний клас RoundingMode {
  державний статичний підсумковий RoundingMode HALF_UP = новий RoundingMode ();
  загальнодоступний статичний кінцевий RoundingMode HALF_EVEN = новий RoundingMode ();

  приватний RoundingMode () {
    // приватна сфера запобігає будь-яким підтипам поза цим класом
  }
}

Тим НЕ менше , то enumконструкція дає різні переваги:

  • Кожен екземпляр toString () друкує ім'я, вказане в коді.
  • (Як уже згадувалося в іншому дописі) змінну типу перерахунку можна порівняти з константами за допомогою switch-caseкеруючої структури.
  • Усі значення перерахунку можна запитувати, використовуючи valuesполе, яке "генерується" для кожного типу перерахування
  • Ось велике порівняння ідентичності wrt: значення enum переживають серіалізацію без клонування.

Серіалізація - велика готчя. Якби я використовував вищезазначений код замість enum, ось як поводиться рівність ідентичності:

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

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

ByteArrayInputStream bais = новий ByteArrayInputStream (baos.toByteArray ());
ObjectInputStream ois = новий ObjectInputStream (bais);
RoundingMode десеріалізований = (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 виходить із шляху, щоб дозволити використовувати тотожність значень enum для перевірки рівності; це заохочена практика.


1
виправлено помилку серіалізації. bugs.sun.com/bugdatabase/view_bug.do?bug_id=6277781
David I.

@DavidI. дякую за оновлення Це дуже непокоїть помилку, і добре знати!
Dilum Ranatunga

1
@DilumRanatunga Я подумав, що це спочатку вплине на мене, але, здається, вони працюють добре після передачі їх через RMI-з'єднання.
Давид І.


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, тому я не можу цього зробити у своєму випадку.
Кіп


Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.