Dlaczego compareTo jest na finale Enum w Javie?

96

Wyliczenie w Javie implementuje Comparableinterfejs. Byłoby miło, aby zastąpić Comparable„s compareTometody, ale tutaj jest oznaczony jako wersja ostateczna. Domyślną naturalny porządek na Enum„s compareTojest podana kolejność.

Czy ktoś wie, dlaczego wyliczenia Java mają to ograniczenie?

neu242
źródło
Jest bardzo dobre wyjaśnienie w Effective Java - 3rd Edition w punkcie 10 (zajmującym się equals (), ale w punkcie 14 mówią, że problem z compareTo () jest taki sam). W skrócie: Jeśli rozszerzysz instancję klasy (np. Wyliczenie) i dodasz składnik wartości, nie możesz zachować kontraktu równości (lub porównania).
Christian H. Kuhn

Odpowiedzi:

122

Myślę, że dla spójności ... kiedy widzisz enumtyp, wiesz na pewno , że jego naturalny porządek to kolejność, w której deklarowane są stałe.

Aby obejść ten problem, możesz łatwo utworzyć własną Comparator<MyEnum>i używać jej, gdy potrzebujesz innej kolejności:

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

Możesz skorzystać Comparatorbezpośrednio z:

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

lub użyj go w kolekcjach lub tablicach:

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

Dalsza informacja:

Zach Scrivena
źródło
Niestandardowe komparatory są naprawdę skuteczne tylko wtedy, gdy dostarczają wyliczenie do kolekcji. Nie pomaga to tak bardzo, jeśli chcesz dokonać bezpośredniego porównania.
Martin OConnor
7
Tak. new MyEnumComparator.compare (enum1, enum2). Gotowe.
Bombe
@martinoconnor & Bombe: Uwzględniłem Twoje komentarze w odpowiedzi. Dzięki!
Zach Scrivena
Ponieważ MyEnumComparatornie ma stanu, powinien to być po prostu singleton, zwłaszcza jeśli robisz to, co sugeruje @Bombe; zamiast tego zrobiłbyś coś takiego, jak MyEnumComparator.INSTANCE.compare(enum1, enum2)uniknięcie niepotrzebnego tworzenia obiektów
kbolino
2
@kbolino: komparator może być zagnieżdżoną klasą wewnątrz klasy wyliczeniowej. Może być przechowywany w LENGTH_COMPARATORstatycznym polu w wyliczeniu. W ten sposób łatwo byłoby go znaleźć dla każdego, kto używa wyliczenia.
Lii
40

Podanie domyślnej implementacji compareTo, która używa kolejności kodu źródłowego, jest w porządku; uczynienie go ostatecznym było błędem ze strony Sun. Porządek porządkowy już uwzględnia kolejność deklaracji. Zgadzam się, że w większości sytuacji programista może po prostu logicznie uporządkować swoje elementy, ale czasami chce się, aby kod źródłowy był zorganizowany w sposób, który sprawia, że ​​czytelność i konserwacja są najważniejsze. Na przykład:


  //===== 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");

Powyższa kolejność wygląda dobrze w kodzie źródłowym, ale autor uważa, że ​​nie tak powinno działać funkcja compareTo. Żądanym zachowaniem compareTo jest uporządkowanie według liczby bajtów. Porządkowanie kodu źródłowego, które by to spowodowało, degraduje organizację kodu.

Jako klient wyliczenia nie obchodziło mnie, jak autor zorganizował swój kod źródłowy. Chciałbym jednak, żeby ich algorytm porównawczy miał jakiś sens. Firma Sun niepotrzebnie związała twórców kodu źródłowego.

Thomas Paine
źródło
4
Zgadzam się, chcę, aby moje wyliczenie miało porównywalny algorytm biznesowy zamiast zamówienia, które może zostać złamane, jeśli ktoś nie zwróci uwagi.
TheBakker
6

Wartości wyliczeń są precyzyjnie uporządkowane logicznie zgodnie z kolejnością, w jakiej są zadeklarowane. Jest to część specyfikacji języka Java. W związku z tym wynika, że ​​wartości wyliczenia można porównać tylko wtedy, gdy są członkami tego samego Enum. Specyfikacja chce dodatkowo zagwarantować, że porównywalna kolejność zwrócona przez funkcję compareTo () jest taka sama, jak kolejność, w której zadeklarowano wartości. To jest właśnie definicja wyliczenia.

Martin OConnor
źródło
Jak wyjaśnił Thomas Paine w swoim przykładzie, język może porządkować tylko składnię, a nie semantykę. Mówisz, że elementy są uporządkowane logicznie, ale tak jak rozumiem wyliczenie, elementy są hermetyzowane za pomocą logicznych środków.
Bondax
2

Jednym z możliwych wyjaśnień jest to, że compareTopowinno być zgodne z equals.

A equalswyliczenia powinny być zgodne z równością tożsamości ( ==).

Gdyby compareTonie było ostateczne, można by zastąpić to zachowaniem, które nie było zgodne z equals, co byłoby bardzo sprzeczne z intuicją.

Lii
źródło
Chociaż zaleca się, aby metoda compareTo () była zgodna z equals (), nie jest to konieczne.
Christian H. Kuhn
-1

Jeśli chcesz zmienić naturalną kolejność elementów wyliczenia, zmień ich kolejność w kodzie źródłowym.

Bombe
źródło
Tak, to właśnie napisałem w oryginalnym wpisie :)
neu242
Tak, ale tak naprawdę nie wyjaśniasz, dlaczego chcesz przesłonić funkcję compareTo (). Więc doszedłem do wniosku, że próbujesz zrobić coś złego ™, a ja starałem się pokazać ci bardziej poprawny sposób.
Bombe
Nie rozumiem, dlaczego miałbym ręcznie sortować wpisy, skoro komputery robią to znacznie lepiej niż ja.
neu242
W wyliczeniach zakłada się, że nie bez powodu uporządkowałeś wpisy w ten konkretny sposób. Jeśli nie ma powodu, lepiej byłoby użyć <enum> .toString (). CompareTo (). A może coś zupełnie innego, jak może zestaw?
Bombe
Powodem tego jest to, że mam Enum z {isocode, countryname}. Został ręcznie posortowany według nazwy kraju, która działa zgodnie z przeznaczeniem. Co się stanie, jeśli zdecyduję, że od teraz chcę sortować według izokodu?
neu242