Warum ist compareTo auf einem Enum-Finale in Java?

93

Eine Aufzählung in Java implementiert die ComparableSchnittstelle. Es wäre schön außer Kraft zu setzen gewesen Comparable‚s compareToMethode, aber hier ist es als final markiert. Der Standard natürliche Ordnung auf Enum‚s compareToist die angegebene Reihenfolge.

Weiß jemand, warum eine Java-Aufzählung diese Einschränkung hat?

neu242
quelle
Es gibt eine sehr gute Erklärung in Effective Java - 3rd Edition in Punkt 10 (Umgang mit equals (), aber in Punkt 14 wird das Problem mit compareTo () als dasselbe bezeichnet). Kurz gesagt: Wenn Sie eine instanziierbare Klasse (wie eine Aufzählung) erweitern und eine Wertkomponente hinzufügen, können Sie den Gleichheitsvertrag (oder den Vergleich mit) nicht beibehalten.
Christian H. Kuhn

Antworten:

121

Aus Gründen der Konsistenz denke ich ... wenn Sie einen enumTyp sehen, wissen Sie sicher , dass seine natürliche Reihenfolge die Reihenfolge ist, in der die Konstanten deklariert werden.

Um dies zu umgehen, können Sie ganz einfach Ihre eigenen erstellen Comparator<MyEnum>und verwenden, wenn Sie eine andere Bestellung benötigen:

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

Sie können das Comparatordirekt verwenden:

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

oder verwenden Sie es in Sammlungen oder Arrays:

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

Weitere Informationen:

Zach Scrivena
quelle
Benutzerdefinierte Komparatoren sind nur dann wirklich effektiv, wenn die Aufzählung einer Sammlung bereitgestellt wird. Es hilft nicht so viel, wenn Sie einen direkten Vergleich anstellen möchten.
Martin OConnor
7
Ja tut es. neuer MyEnumComparator.compare (enum1, enum2). Et voilá.
Bombe
@martinoconnor & Bombe: Ich habe Ihre Kommentare in die Antwort aufgenommen. Vielen Dank!
Zach Scrivena
Da MyEnumComparatores keinen Status hat, sollte es nur ein Singleton sein, insbesondere wenn Sie das tun, was @Bombe vorschlägt. Stattdessen würden Sie so etwas wie MyEnumComparator.INSTANCE.compare(enum1, enum2)unnötige Objekterstellung vermeiden
kbolino
2
@kbolino: Der Komparator kann eine verschachtelte Klasse innerhalb der Enum-Klasse sein. Es könnte in einem LENGTH_COMPARATORstatischen Feld in der Aufzählung gespeichert werden . Auf diese Weise wäre es für jeden, der die Aufzählung verwendet, leicht zu finden.
Lii
39

Es ist in Ordnung, eine Standardimplementierung von compareTo bereitzustellen, die die Quellcode-Reihenfolge verwendet. es endgültig zu machen war ein Fehltritt von Suns Seite. Die Ordnungszahl berücksichtigt bereits die Deklarationsreihenfolge. Ich bin damit einverstanden, dass ein Entwickler in den meisten Situationen seine Elemente nur logisch ordnen kann, aber manchmal möchte man, dass der Quellcode so organisiert ist, dass Lesbarkeit und Wartung an erster Stelle stehen. Beispielsweise:


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

Die obige Reihenfolge sieht im Quellcode gut aus, aber der Autor ist nicht der Meinung, dass compareTo funktionieren sollte. Das gewünschte compareTo-Verhalten besteht darin, dass die Reihenfolge nach Anzahl der Bytes erfolgt. Die Reihenfolge des Quellcodes, die dies bewirken würde, verschlechtert die Organisation des Codes.

Als Kunde einer Aufzählung ist es mir egal, wie der Autor seinen Quellcode organisiert hat. Ich möchte jedoch, dass ihr Vergleichsalgorithmus einen Sinn ergibt. Sun hat Quellcode-Schreiber unnötigerweise in eine Bindung gebracht.

Thomas Paine
quelle
3
Ich stimme zu, ich möchte, dass meine Aufzählung in der Lage ist, einen vergleichbaren Algorithmus zu haben, anstatt eine Bestellung, die gebrochen werden kann, wenn jemand nicht aufpasst.
TheBakker
6

Aufzählungswerte werden logisch genau in der Reihenfolge sortiert, in der sie deklariert sind. Dies ist Teil der Java-Sprachspezifikation. Daraus folgt, dass Aufzählungswerte nur verglichen werden können, wenn sie Mitglieder derselben Aufzählung sind. Die Spezifikation möchte ferner sicherstellen, dass die von compareTo () zurückgegebene vergleichbare Reihenfolge mit der Reihenfolge übereinstimmt, in der die Werte deklariert wurden. Dies ist die Definition einer Aufzählung.

Martin OConnor
quelle
Wie Thomas Paine in seinem Beispiel deutlich machte, kann die Sprache nur nach Syntaktik und nicht nach Semantik geordnet werden. Sie sagen, die Artikel sind logisch geordnet , aber so wie ich Enum verstehe, sind die Artikel mit logischen Mitteln gekapselt .
Bondax
2

Eine mögliche Erklärung ist, dass compareTomit übereinstimmen sollte equals.

Und equalsfür Aufzählungen sollte mit Identitätsgleichheit übereinstimmen ( ==).

Wenn compareToes nicht endgültig ist, kann es mit einem Verhalten überschrieben werden, das nicht mit equalsdem übereinstimmt , was sehr kontraintuitiv wäre.

Lii
quelle
Es wird zwar empfohlen, compareTo () mit equals () zu konsistent, dies ist jedoch nicht erforderlich.
Christian H. Kuhn
-1

Wenn Sie die natürliche Reihenfolge der Elemente Ihrer Aufzählung ändern möchten, ändern Sie deren Reihenfolge im Quellcode.

Bombe
quelle
Ja, das habe ich im Originaleintrag geschrieben :)
neu242
Ja, aber Sie erklären nicht wirklich, warum Sie compareTo () überschreiben möchten. Mein Fazit war also, dass Sie versuchen, etwas Schlechtes zu tun, und ich habe versucht, Ihnen einen korrekteren Weg zu zeigen.
Bombe
Ich verstehe nicht, warum ich die Einträge von Hand sortieren muss, wenn Computer das viel besser machen als ich.
Neu242
In Aufzählungen wird davon ausgegangen, dass Sie die Einträge aus einem bestimmten Grund auf diese bestimmte Weise bestellt haben. Wenn es keinen Grund gibt, sollten Sie <enum> .toString (). CompareTo () verwenden. Oder vielleicht etwas ganz anderes, wie vielleicht ein Set?
Bombe
Der Grund dafür ist, dass ich eine Aufzählung mit {isocode, countryyname} habe. Es wurde manuell nach Ländernamen sortiert, was wie beabsichtigt funktioniert. Was ist, wenn ich mich entscheide, von nun an nach Isocode zu sortieren?
Neu242