Perché compareTo su una finale Enum in Java?


93

Un enum in Java implementa l' Comparableinterfaccia. Sarebbe stato bello sovrascrivere Comparableil compareTometodo di, ma qui è contrassegnato come definitivo. L'ordine naturale predefinito di Enums compareToè l'ordine elencato.

Qualcuno sa perché una enumerazione Java ha questa restrizione?


C'è una spiegazione molto fine in Effective Java - 3rd Edition nell'Articolo 10 (che tratta di equals (), ma nell'Articolo 14 dicono che il Problema con compareTo () è lo stesso). In breve: se estendi una classe istanziabile (come un'enumerazione) e aggiungi un componente valore, non puoi mantenere il contratto uguale (o compareTo).
Christian H. Kuhn

Risposte:


121

Per coerenza immagino ... quando vedi un enumtipo, sai per certo che il suo ordinamento naturale è l'ordine in cui vengono dichiarate le costanti.

Per ovviare a questo problema, puoi facilmente crearne uno tuo Comparator<MyEnum>e usarlo ogni volta che hai bisogno di un ordine diverso:

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

Puoi usare Comparatordirettamente:

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

o usalo in collezioni o array:

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

Ulteriori informazioni:


I comparatori personalizzati sono realmente efficaci solo quando si fornisce l'Enum a una raccolta. Non aiuta molto se vuoi fare un confronto diretto.
Martin OConnor

7
Sì, lo fa. new MyEnumComparator.compare (enum1, enum2). Et voilà.
Bombe

@martinoconnor & Bombe: ho incorporato i tuoi commenti nella risposta. Grazie!
Zach Scrivena

Dal momento che MyEnumComparatornon ha uno stato, dovrebbe essere solo un singleton, soprattutto se stai facendo ciò che suggerisce @Bombe; invece, faresti qualcosa come MyEnumComparator.INSTANCE.compare(enum1, enum2)evitare la creazione di oggetti non necessari
kbolino

2
@kbolino: l'evento di confronto potrebbe essere una classe nidificata all'interno della classe enum. Potrebbe essere memorizzato in un LENGTH_COMPARATORcampo statico nell'enum. In questo modo sarebbe facile trovarlo per chiunque utilizzi l'enumerazione.
Lii

39

Fornire un'implementazione predefinita di compareTo che utilizza l'ordinamento del codice sorgente va bene; renderlo definitivo è stato un passo falso da parte di Sun. L'ordinale conta già per l'ordine di dichiarazione. Sono d'accordo sul fatto che nella maggior parte delle situazioni uno sviluppatore possa semplicemente ordinare logicamente i propri elementi, ma a volte si desidera che il codice sorgente sia organizzato in modo da rendere la leggibilità e la manutenzione di primaria importanza. Per esempio:


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

L'ordinamento di cui sopra sembra buono nel codice sorgente, ma non è il modo in cui l'autore crede che il confrontoTo dovrebbe funzionare. Il comportamento di compareTo desiderato consiste nell'ordinamento in base al numero di byte. L'ordinamento del codice sorgente che lo renderebbe possibile degrada l'organizzazione del codice.

Come cliente di un'enumerazione, non mi potrebbe importare di meno come l'autore ha organizzato il proprio codice sorgente. Tuttavia, voglio che il loro algoritmo di confronto abbia un senso. Sun ha inutilmente messo in difficoltà gli autori del codice sorgente.


3
D'accordo, voglio che il mio enum sia in grado di avere un algoritmo comparabile al business invece di un ordine che può essere infranto se qualcuno non presta attenzione.
Bakker

6

I valori di enumerazione sono ordinati con precisione in modo logico in base all'ordine in cui sono dichiarati. Questo fa parte della specifica del linguaggio Java. Pertanto ne consegue che i valori di enumerazione possono essere confrontati solo se sono membri dello stesso Enum. La specifica vuole ulteriormente garantire che l'ordine comparabile restituito da compareTo () sia lo stesso dell'ordine in cui sono stati dichiarati i valori. Questa è la definizione stessa di un'enumerazione.


Come ha chiarito Thomas Paine nel suo esempio, la lingua può essere ordinata solo per sintattica, non per semantica. Dici, gli elementi sono ordinati logicamente, ma per come intendo enum, gli elementi sono incapsulati in modo logico.
Bondax

2

Una possibile spiegazione è che compareTodovrebbe essere coerente con equals.

E equalsper le enumerazioni dovrebbe essere coerente con l'uguaglianza di identità ( ==).

Se compareTodove essere non definitivo sarebbe possibile sovrascriverlo con un comportamento non coerente con equals, il che sarebbe molto contro-intuitivo.


Sebbene sia consigliabile che compareTo () sia coerente con equals (), non è necessario.
Christian H. Kuhn

-1

Se vuoi cambiare l'ordine naturale degli elementi della tua enum, cambia il loro ordine nel codice sorgente.


Sì, è quello che ho scritto nella voce originale :)
neu242

Sì, ma non spieghi davvero perché vorresti sovrascrivere compareTo (). Quindi la mia conclusione è stata che stai cercando di fare Something Bad ™ e io stavo cercando di mostrarti un modo più corretto.
Bombe

Non vedo perché dovrei ordinare le voci a mano quando i computer lo fanno molto meglio di me.
neu242

Nelle enumerazioni si presume che tu abbia ordinato le voci in quel modo specifico per un motivo. Se non c'è motivo, è meglio usare <enum> .toString (). CompareTo (). O forse qualcosa di completamente diverso, come forse un Set?
Bombe

Il motivo per cui è venuto fuori è che ho un Enum con {isocode, countryname}. È stato ordinato manualmente su countryname, che funziona come previsto. E se decido di ordinare in base all'isocodice d'ora in poi?
neu242
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.