Pourquoi compareTo sur un Enum final en Java?

93

Une énumération en Java implémente l' Comparableinterface. Cela aurait été bien de surcharger Comparablela compareTométhode, mais ici, elle est marquée comme définitive. L'ordre naturel par défaut sur Enumles compareToest l'ordre indiqué.

Est-ce que quelqu'un sait pourquoi une énumération Java a cette restriction?

neu242
la source
Il y a une très bonne explication dans Effective Java - 3e édition dans l'élément 10 (traitant de equals (), mais dans l'élément 14, ils indiquent que le problème avec compareTo () est le même). En bref: si vous étendez une classe instanciable (comme une énumération) et ajoutez un composant de valeur, vous ne pouvez pas conserver le contrat equals (ou compareTo).
Christian H.Kuhn

Réponses:

121

Par souci de cohérence, je suppose ... quand vous voyez un enumtype, vous savez pertinemment que son ordre naturel est l'ordre dans lequel les constantes sont déclarées.

Pour contourner ce problème, vous pouvez facilement créer le vôtre Comparator<MyEnum>et l'utiliser chaque fois que vous avez besoin d'un ordre différent:

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

Vous pouvez utiliser Comparatordirectement:

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

ou utilisez-le dans des collections ou des tableaux:

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

Informations complémentaires:

Zach Scrivena
la source
Les comparateurs personnalisés ne sont vraiment efficaces que lors de la fourniture de Enum à une collection. Cela n'aide pas autant si vous voulez faire une comparaison directe.
Martin OConnor le
7
Oui. new MyEnumComparator.compare (enum1, enum2). Et voilà.
Bombe le
@martinoconnor & Bombe: J'ai incorporé vos commentaires dans la réponse. Merci!
Zach Scrivena le
Puisqu'il MyEnumComparatorn'y a pas d'état, il ne devrait s'agir que d'un singleton, surtout si vous faites ce que @Bombe suggère; à la place, vous feriez quelque chose comme MyEnumComparator.INSTANCE.compare(enum1, enum2)pour éviter la création d'objets inutiles
kbolino
2
@kbolino: Le comparateur pourrait être une classe imbriquée dans la classe enum. Il pourrait être stocké dans un LENGTH_COMPARATORchamp statique de l'énumération. De cette façon, il serait facile de trouver pour quiconque utilise l'énumération.
Lii
39

Fournir une implémentation par défaut de compareTo qui utilise l'ordre du code source est très bien; le rendre définitif était un faux pas de la part de Sun. L'ordinal tient déjà compte de l'ordre de déclaration. Je suis d'accord que dans la plupart des situations, un développeur peut simplement ordonner logiquement ses éléments, mais parfois on veut que le code source soit organisé de manière à rendre la lisibilité et la maintenance primordiales. Par exemple:


  //===== 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'ordre ci-dessus semble bon dans le code source, mais ce n'est pas la façon dont l'auteur pense que compareTo devrait fonctionner. Le comportement compareTo souhaité est que le tri soit par nombre d'octets. L'ordre du code source qui permettrait que cela se produise dégrade l'organisation du code.

En tant que client d'une énumération, je me fiche de la manière dont l'auteur a organisé son code source. Je veux cependant que leur algorithme de comparaison ait un sens. Sun a inutilement mis les écrivains de code source dans une liaison.

Thomas Paine
la source
3
D'accord, je veux que mon énumération puisse avoir un algorithme commercial comparable au lieu d'un ordre qui peut être rompu si quelqu'un ne fait pas attention.
TheBakker
6

Les valeurs d'énumération sont ordonnées logiquement avec précision en fonction de l'ordre dans lequel elles sont déclarées. Cela fait partie de la spécification du langage Java. Par conséquent, il s'ensuit que les valeurs d'énumération ne peuvent être comparées que si elles sont membres du même Enum. La spécification veut en outre garantir que l'ordre comparable renvoyé par compareTo () est le même que l'ordre dans lequel les valeurs ont été déclarées. C'est la définition même d'une énumération.

Martin OConnor
la source
Comme Thomas Paine l'a clairement indiqué dans son exemple, le langage ne peut ordonner que par syntaxique et non par sémantique. Vous dites que les éléments sont classés logiquement, mais d'après ma compréhension de l'énumération, les éléments sont encapsulés par des moyens logiques.
Bondax
2

Une explication possible est que cela compareTodevrait être cohérent avec equals.

Et equalspour les énumérations doivent être cohérentes avec l'égalité d'identité ( ==).

Si compareTooù être non définitif, il serait possible de le remplacer par un comportement qui n'est pas cohérent avec equals, ce qui serait très contre-intuitif.

Lii
la source
Bien qu'il soit recommandé que compareTo () soit cohérent avec equals (), ce n'est pas nécessaire.
Christian H.Kuhn
-1

Si vous souhaitez modifier l'ordre naturel des éléments de votre énumération, modifiez leur ordre dans le code source.

Bombe
la source
Ouais, c'est ce que j'ai écrit dans l'entrée originale :)
neu242
Oui, mais vous n'expliquez pas vraiment pourquoi vous voudriez remplacer compareTo (). Donc ma conclusion était que vous essayez de faire Something Bad ™ et j'essayais de vous montrer une manière plus correcte.
Bombe le
Je ne vois pas pourquoi je devrais avoir à trier les entrées à la main alors que les ordinateurs font beaucoup mieux que moi.
neu242
Dans les énumérations, il est supposé que vous avez ordonné les entrées de cette manière spécifique pour une raison. S'il n'y a aucune raison, il vaut mieux utiliser <enum> .toString (). CompareTo (). Ou peut-être quelque chose de complètement différent, comme peut-être un ensemble?
Bombe le
La raison pour laquelle cela est apparu est que j'ai un Enum avec {isocode, countryname}. Il a été trié manuellement sur le nom du pays, qui fonctionne comme prévu. Et si je décide que je veux trier par isocode à partir de maintenant?
neu242