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