Почему compareTo в финале Enum в Java?

95

Перечисление в Java реализует Comparableинтерфейс. Было бы хорошо , чтобы переопределить Comparable«s compareToметод, но здесь он помечен как окончательный. Естественный порядок по умолчанию на Enum«s compareToявляется перечисленным порядком.

Кто-нибудь знает, почему у перечислений Java есть это ограничение?

neu242
источник
В «Эффективной Java - 3-е издание» в правиле 10 есть прекрасное объяснение (касается equals (), но в правиле 14 говорится, что проблема с compareTo () такая же). Вкратце: если вы расширяете экземпляр класса (например, перечисление) и добавляете компонент значения, вы не можете сохранить контракт equals (или compareTo).
Christian H. Kuhn

Ответы:

121

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

Чтобы обойти это, вы можете легко создать свой собственный Comparator<MyEnum>и использовать его всякий раз, когда вам нужен другой порядок:

enum MyEnum
{
    DOG("woof"),
    CAT("meow");

    String sound;    
    MyEnum(String s) { sound = s; }
}

class MyEnumComparator implements Comparator<MyEnum>
{
    public int compare(MyEnum o1, MyEnum o2)
    {
        return -o1.compareTo(o2); // this flips the order
        return o1.sound.length() - o2.sound.length(); // this compares length
    }
}

Вы можете использовать Comparatorнапрямую:

MyEnumComparator c = new MyEnumComparator();
int order = c.compare(MyEnum.CAT, MyEnum.DOG);

или используйте его в коллекциях или массивах:

NavigableSet<MyEnum> set = new TreeSet<MyEnum>(c);
MyEnum[] array = MyEnum.values();
Arrays.sort(array, c);    

Дальнейшая информация:

Зак Скривена
источник
Пользовательские компараторы действительно эффективны только при передаче Enum в коллекцию. Если вы хотите провести прямое сравнение, это не поможет.
Мартин ОКоннор,
7
Да. новый MyEnumComparator.compare (enum1, enum2). И вуаля.
Bombe
@martinoconnor & Bombe: Я включил ваши комментарии в ответ. Благодарность!
Зак Скривена, 06
Поскольку MyEnumComparatorне имеет состояния, это должно быть просто синглтон, особенно если вы делаете то, что предлагает @Bombe; вместо этого вы бы сделали что-нибудь, MyEnumComparator.INSTANCE.compare(enum1, enum2)чтобы избежать создания ненужных объектов
kbolino
2
@kbolino: событие сравнения может быть вложенным классом внутри класса enum. Его можно сохранить в LENGTH_COMPARATORстатическом поле перечисления. Таким образом, будет легко найти любого, кто использует перечисление.
Lii
40

Предоставление реализации по умолчанию compareTo, которая использует порядок исходного кода, нормально; сделать это окончательным было ошибкой со стороны Sun. Порядок декларации уже учитывается порядковым номером. Я согласен с тем, что в большинстве ситуаций разработчик может просто логически упорядочить свои элементы, но иногда нужно, чтобы исходный код был организован таким образом, чтобы удобство чтения и обслуживание были первостепенными. Например:


  //===== SI BYTES (10^n) =====//

  /** 1,000 bytes. */ KILOBYTE (false, true,  3, "kB"),
  /** 106 bytes. */   MEGABYTE (false, true,  6, "MB"),
  /** 109 bytes. */   GIGABYTE (false, true,  9, "GB"),
  /** 1012 bytes. */  TERABYTE (false, true, 12, "TB"),
  /** 1015 bytes. */  PETABYTE (false, true, 15, "PB"),
  /** 1018 bytes. */  EXABYTE  (false, true, 18, "EB"),
  /** 1021 bytes. */  ZETTABYTE(false, true, 21, "ZB"),
  /** 1024 bytes. */  YOTTABYTE(false, true, 24, "YB"),

  //===== IEC BYTES (2^n) =====//

  /** 1,024 bytes. */ KIBIBYTE(false, false, 10, "KiB"),
  /** 220 bytes. */   MEBIBYTE(false, false, 20, "MiB"),
  /** 230 bytes. */   GIBIBYTE(false, false, 30, "GiB"),
  /** 240 bytes. */   TEBIBYTE(false, false, 40, "TiB"),
  /** 250 bytes. */   PEBIBYTE(false, false, 50, "PiB"),
  /** 260 bytes. */   EXBIBYTE(false, false, 60, "EiB"),
  /** 270 bytes. */   ZEBIBYTE(false, false, 70, "ZiB"),
  /** 280 bytes. */   YOBIBYTE(false, false, 80, "YiB");

Вышеупомянутый порядок выглядит хорошо в исходном коде, но не так, как считает автор compareTo. Желаемое поведение compareTo состоит в том, чтобы упорядочивать по количеству байтов. Упорядочивание исходного кода, которое могло бы сделать это, ухудшает организацию кода.

Мне, как клиенту перечисления, наплевать, как автор организовал свой исходный код. Однако я хочу, чтобы их алгоритм сравнения имел какой-то смысл. Sun без надобности связывает разработчиков исходного кода.

Томас Пейн
источник
4
Согласитесь, я хочу, чтобы у моего enum был алгоритм, сопоставимый с бизнесом, а не порядок, который может быть нарушен, если кто-то не обратит внимания.
TheBakker
6

Значения перечисления точно упорядочены логически в соответствии с порядком их объявления. Это часть спецификации языка Java. Следовательно, значения перечисления можно сравнивать, только если они являются членами одного и того же Enum. Спецификация хочет дополнительно гарантировать, что сопоставимый порядок, возвращаемый функцией compareTo (), совпадает с порядком, в котором были объявлены значения. Это само определение перечисления.

Мартин ОКоннор
источник
Как пояснил Томас Пейн в своем примере, в языке можно упорядочить только синтаксис, но не семантику. Вы говорите, что элементы упорядочены логически, но, как я понимаю, перечисление, элементы инкапсулируются логическими средствами.
Bondax
2

Одно из возможных объяснений состоит в том, что оно compareToдолжно соответствовать equals.

И equalsдля перечислений должно быть согласовано равенство идентичности ( ==).

Если compareToбы он не был окончательным, его можно было бы переопределить с помощью поведения, которое не соответствовало бы equals, что было бы очень нелогично.

Lii
источник
Хотя рекомендуется, чтобы compareTo () согласовывался с equals (), в этом нет необходимости.
Christian H. Kuhn
-1

Если вы хотите изменить естественный порядок элементов перечисления, измените их порядок в исходном коде.

Бомба
источник
Ага, это то, что я написал в исходной записи :)
neu242 06
Да, но вы действительно не объясняете, почему вы хотите переопределить compareTo (). Я пришел к выводу, что вы пытаетесь сделать что-то плохое ™, и я пытался показать вам более правильный путь.
Bombe
Я не понимаю, зачем мне сортировать записи вручную, когда компьютеры делают это намного лучше меня.
neu242 06
В перечислениях предполагается, что вы упорядочили записи таким образом по определенной причине. Если нет причин, лучше использовать <enum> .toString (). CompareTo (). Или, может быть, что-то совсем другое, например, Сет?
Bombe
Причина в том, что у меня есть Enum с {isocode, countryname}. Он был вручную отсортирован по названию страны, что работает должным образом. Что, если я решу, что с этого момента хочу сортировать по изокоду?
neu242 06