¿Por qué compareTo en una Enum final en Java?

93

Una enumeración en Java implementa la Comparableinterfaz. Hubiera sido bueno anular Comparableel compareTométodo, pero aquí está marcado como final. El orden natural por defecto en Enum's compareToes el orden indicado.

¿Alguien sabe por qué las enumeraciones de Java tienen esta restricción?

neu242
fuente
Hay una explicación muy fina en Effective Java - 3rd Edition en el ítem 10 (trata con equals (), pero en el ítem 14 dicen que el problema con compareTo () es el mismo). En resumen: si extiende una clase instanciable (como una enumeración) y agrega un componente de valor, no puede conservar el contrato igual (o compareTo).
Christian H. Kuhn

Respuestas:

121

Por coherencia, supongo ... cuando ve un enumtipo, sabe a ciencia cierta que su orden natural es el orden en el que se declaran las constantes.

Para solucionar esto, puede crear fácilmente el suyo propio Comparator<MyEnum>y usarlo siempre que necesite un pedido diferente:

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

Puede utilizar Comparatordirectamente:

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

o úselo en colecciones o matrices:

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

Más información:

Zach Scrivena
fuente
Los comparadores personalizados solo son realmente efectivos cuando se suministra el Enum a una colección. No ayuda tanto si desea hacer una comparación directa.
Martin OConnor
7
Sí lo hace. new MyEnumComparator.compare (enum1, enum2). Et voilá.
Bombe
@martinoconnor & Bombe: He incorporado sus comentarios a la respuesta. ¡Gracias!
Zach Scrivena
Como MyEnumComparatorno tiene estado, debería ser un singleton, especialmente si está haciendo lo que sugiere @Bombe; en su lugar, haría algo como MyEnumComparator.INSTANCE.compare(enum1, enum2)evitar la creación de objetos innecesarios
kbolino
2
@kbolino: El comparador podría ser una clase anidada dentro de la clase enum. Podría almacenarse en un LENGTH_COMPARATORcampo estático en la enumeración. De esa manera, sería fácil de encontrar para cualquiera que esté usando la enumeración.
Lii
39

Proporcionar una implementación predeterminada de compareTo que utilice el orden del código fuente está bien; hacerla definitiva fue un paso en falso por parte de Sun. El ordinal ya representa el orden de declaración. Estoy de acuerdo en que, en la mayoría de las situaciones, un desarrollador puede simplemente ordenar lógicamente sus elementos, pero a veces uno quiere que el código fuente esté organizado de una manera que haga que la legibilidad y el mantenimiento sean primordiales. Por ejemplo:


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

El orden anterior se ve bien en el código fuente, pero no es como el autor cree que debe funcionar compareTo. El comportamiento deseado de compareTo es que el orden sea por número de bytes. El ordenamiento del código fuente que haría que eso suceda degrada la organización del código.

Como cliente de una enumeración, no podría importarme menos cómo el autor organizó su código fuente. Sin embargo, quiero que su algoritmo de comparación tenga algún tipo de sentido. Sun ha puesto innecesariamente a los escritores de código fuente en un aprieto.

Thomas Paine
fuente
3
De acuerdo, quiero que mi enumeración pueda tener un algoritmo comercial comparable en lugar de un pedido que se puede romper si alguien no presta atención.
TheBakker
6

Los valores de enumeración se ordenan lógicamente con precisión según el orden en que se declaran. Esto es parte de la especificación del lenguaje Java. Por lo tanto, se deduce que los valores de enumeración solo se pueden comparar si son miembros de la misma Enum. La especificación quiere garantizar aún más que el orden comparable devuelto por compareTo () es el mismo que el orden en el que se declararon los valores. Ésta es la definición misma de enumeración.

Martín OConnor
fuente
Como dejó claro Thomas Paine en su ejemplo, el lenguaje solo puede ordenarse por sintáctica, no por semántica. Usted dice que los elementos están ordenados lógicamente, pero según entiendo enum, los elementos están encapsulados por medios lógicos.
Bondax
2

Una posible explicación es que compareTodebería ser coherente con equals.

Y las equalsenumeraciones deben ser coherentes con la igualdad de identidad ( ==).

Si compareTono fuera definitivo, sería posible anularlo con un comportamiento que no fuera coherente equals, lo que sería muy contrario a la intuición.

Lii
fuente
Si bien se recomienda que compareTo () sea coherente con equals (), no es necesario.
Christian H. Kuhn
-1

Si desea cambiar el orden natural de los elementos de su enumeración, cambie su orden en el código fuente.

Bombe
fuente
Sí, eso es lo que escribí en la entrada original :)
neu242
Sí, pero realmente no explica por qué querría anular compareTo (). Entonces mi conclusión fue que estás tratando de hacer Something Bad ™ y yo estaba tratando de mostrarte una forma más correcta.
Bombe
No veo por qué debería tener que ordenar las entradas a mano cuando las computadoras lo hacen mucho mejor que yo.
neu242
En las enumeraciones se supone que ordenó las entradas de esa manera específica por una razón. Si no hay ninguna razón, es mejor usar <enum> .toString (). CompareTo (). ¿O quizás algo completamente diferente, como quizás un set?
Bombe
La razón por la que surgió esto es que tengo una enumeración con {isocode, countryname}. Se ordenó manualmente por nombre de país, que funciona según lo previsto. ¿Qué pasa si decido que quiero ordenar por isocode de ahora en adelante?
neu242