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?


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:


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.


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.


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.


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.


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
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.