Por que compareTo em um Enum final em Java?

93

Um enum em Java implementa a Comparableinterface. Teria sido bom substituir Comparableo compareTométodo de, mas aqui está marcado como final. A ordem natural padrão em Enum's compareToé a ordem listada.

Alguém sabe por que enums Java tem essa restrição?

neu242
fonte
Há uma explicação muito fina em Effective Java - 3rd Edition no Item 10 (lidando com equals (), mas no Item 14 eles dizem que o problema com compareTo () é o mesmo). Resumindo: se você estender uma classe instanciável (como um enum) e adicionar um componente de valor, não poderá preservar o contrato igual a (ou compareTo).
Christian H. Kuhn

Respostas:

121

Por consistência, eu acho ... quando você vê um enumtipo, sabe com certeza que sua ordem natural é a ordem em que as constantes são declaradas.

Para contornar isso, você pode criar facilmente o seu próprio Comparator<MyEnum>e usá-lo sempre que precisar de um 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
    }
}

Você pode usar Comparatordiretamente:

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

ou usá-lo em coleções ou arrays:

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

Outras informações:

Zach Scrivena
fonte
Os comparadores personalizados só são realmente eficazes ao fornecer o Enum a uma coleção. Não ajuda muito se você quiser fazer uma comparação direta.
Martin OConnor
7
Sim. new MyEnumComparator.compare (enum1, enum2). Et voilá.
Bombe
@martinoconnor & Bombe: Incorporei seus comentários à resposta. Obrigado!
Zach Scrivena
Como MyEnumComparatornão tem estado, deve ser apenas um singleton, especialmente se você estiver fazendo o que @Bombe sugere; em vez disso, você faria algo como MyEnumComparator.INSTANCE.compare(enum1, enum2)evitar a criação desnecessária de objetos
kbolino
2
@kbolino: O comparador pode ser uma classe aninhada dentro da classe enum. Ele pode ser armazenado em um LENGTH_COMPARATORcampo estático no enum. Dessa forma, seria fácil localizar para qualquer pessoa que esteja usando o enum.
Lii
39

Fornecer uma implementação padrão de compareTo que usa a ordem do código-fonte está bom; torná-lo final foi um erro da parte da Sun. O ordinal já responde pela ordem de declaração. Eu concordo que na maioria das situações um desenvolvedor pode apenas ordenar logicamente seus elementos, mas às vezes alguém quer o código-fonte organizado de uma maneira que torne a legibilidade e a manutenção fundamentais. Por exemplo:


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

A ordem acima parece boa no código-fonte, mas não é como o autor acredita que compareTo deve funcionar. O comportamento desejado compareTo é ter a ordenação por número de bytes. A ordem do código-fonte que faria isso acontecer degrada a organização do código.

Como cliente de uma enumeração, não poderia me importar menos como o autor organizou seu código-fonte. Eu quero que seu algoritmo de comparação faça algum sentido, no entanto. A Sun colocou desnecessariamente os escritores de código-fonte em um beco sem saída.

Thomas Paine
fonte
3
Concordo, eu quero que meu enum seja capaz de ter um algoritmo de negócios comparável em vez de um pedido que pode ser quebrado se alguém não prestar atenção.
TheBakker de
6

Os valores de enumeração são precisamente ordenados logicamente de acordo com a ordem em que são declarados. Isso faz parte da especificação da linguagem Java. Portanto, segue-se que os valores de enumeração só podem ser comparados se forem membros do mesmo Enum. A especificação deseja garantir ainda mais que a ordem comparável retornada por compareTo () seja a mesma na qual os valores foram declarados. Esta é a própria definição de enumeração.

Martin OConnor
fonte
Como Thomas Paine deixou claro em seu exemplo, a linguagem só pode ordenar por sintaxe, não semântica. Você diz que os itens são ordenados logicamente, mas pelo que entendi enum, os itens são encapsulados por meios lógicos.
Bondax
2

Uma explicação possível é que compareTodeve ser consistente com equals.

E equalspara enums deve ser consistente com a igualdade de identidade ( ==).

Se compareTofosse não final, seria possível substituí-lo por um comportamento que não era consistente com equals, o que seria muito contra-intuitivo.

Lii
fonte
Embora seja recomendado que compareTo () seja consistente com equals (), isso não é necessário.
Christian H. Kuhn
-1

Se você quiser alterar a ordem natural dos elementos de seu enum, altere sua ordem no código-fonte.

Bombe
fonte
Sim, foi o que escrevi na entrada original :)
neu242
Sim, mas você realmente não explica por que deseja substituir compareTo (). Portanto, minha conclusão foi que você está tentando fazer algo ruim ™ e eu estava tentando mostrar uma maneira mais correta.
Bombe
Não vejo por que deveria ordenar as entradas manualmente, quando os computadores fazem isso muito melhor do que eu.
neu242
Nas enumerações, presume-se que você ordenou as entradas dessa maneira específica por um motivo. Se não houver razão, é melhor usar <enum> .toString (). CompareTo (). Ou talvez algo totalmente diferente, como talvez um Conjunto?
Bombe
A razão pela qual isso surgiu é que eu tenho um Enum com {isocode, countryname}. Ele foi classificado manualmente em countryname, que funciona conforme o esperado. E se eu decidir que quero classificar por isocódigo a partir de agora?
neu242