Mengapa bandingkanTo pada final Enum di Java?

95

Enum di Java mengimplementasikan Comparableantarmuka. Ini akan menyenangkan untuk override Comparable's compareTometode, tapi di sini itu ditandai sebagai final. Default tatanan alam di Enum's compareToadalah urutan yang tercantum.

Adakah yang tahu mengapa enum Java memiliki batasan ini?

neu242
sumber
Ada penjelasan yang sangat bagus di Efektif Java - Edisi ke-3 di Butir 10 (berurusan dengan sama dengan (), tetapi di Butir 14 mereka mengatakan Masalah dengan bandingkanTo () adalah sama). Singkatnya: Jika Anda memperluas kelas instantiable (seperti enum) dan menambahkan komponen nilai, Anda tidak dapat mempertahankan kontrak sama (atau bandingkanTo).
Christian H. Kuhn

Jawaban:

121

Untuk konsistensi saya kira ... ketika Anda melihat sebuah enumtipe, Anda tahu pasti bahwa urutan alaminya adalah urutan di mana konstanta dideklarasikan.

Untuk mengatasinya, Anda dapat dengan mudah membuat sendiri Comparator<MyEnum>dan menggunakannya kapan pun Anda membutuhkan pemesanan yang berbeda:

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

Anda dapat menggunakan Comparatorlangsung:

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

atau gunakan dalam koleksi atau array:

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

Informasi lebih lanjut:

Zach Scrivena
sumber
Komparator kustom hanya benar-benar efektif saat memasok Enum ke Koleksi. Tidak banyak membantu jika Anda ingin membuat perbandingan langsung.
Martin OConnor
7
Ya, benar. MyEnumComparator.compare baru (enum1, enum2). Et voilá.
Bombe
@martinoconnor & Bombe: Saya telah memasukkan komentar Anda ke dalam jawaban. Terima kasih!
Zach Scrivena
Karena MyEnumComparatortidak memiliki status, itu seharusnya hanya tunggal, terutama jika Anda melakukan apa yang disarankan @Bombe; sebagai gantinya, Anda akan melakukan sesuatu seperti MyEnumComparator.INSTANCE.compare(enum1, enum2)menghindari pembuatan objek yang tidak perlu
kbolino
2
@kbolino: Pembanding acara bisa menjadi kelas bersarang di dalam kelas enum. Itu bisa disimpan dalam LENGTH_COMPARATORbidang statis di enum. Dengan cara itu akan mudah untuk menemukan siapa saja yang menggunakan enum.
Lii
40

Menyediakan implementasi default dari bandingkanTo yang menggunakan pengurutan kode sumber tidak masalah; menyelesaikannya adalah salah langkah di pihak Sun. Ordinal sudah memperhitungkan perintah deklarasi. Saya setuju bahwa dalam kebanyakan situasi, seorang pengembang hanya dapat secara logis memesan elemen mereka, tetapi terkadang seseorang ingin kode sumber diatur sedemikian rupa sehingga keterbacaan dan pemeliharaan menjadi yang terpenting. Sebagai contoh:


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

Urutan di atas terlihat bagus dalam kode sumber, tetapi tidak seperti yang diyakini penulis tentang cara membandingkanTo. Perilaku bandingkan yang diinginkan adalah mengurutkan berdasarkan jumlah byte. Pengurutan kode sumber yang akan membuat hal itu terjadi akan menurunkan pengaturan kode.

Sebagai klien enumerasi saya tidak peduli bagaimana penulis mengatur kode sumber mereka. Saya memang ingin algoritme perbandingan mereka masuk akal. Sun tidak perlu membuat penulis kode sumber terikat.

Thomas Paine
sumber
4
Setuju, saya ingin enum saya dapat memiliki bisnis yang sebanding dengan algorythm alih-alih pesanan yang dapat rusak jika seseorang tidak memperhatikan.
TheBakker
6

Nilai pencacahan diurutkan secara tepat secara logis sesuai dengan urutan pernyataannya. Ini adalah bagian dari spesifikasi bahasa Java. Oleh karena itu, nilai pencacahan hanya dapat dibandingkan jika mereka adalah anggota Enum yang sama. Spesifikasi ingin lebih menjamin bahwa urutan pembanding yang dikembalikan oleh bandingkanTo () sama dengan urutan di mana nilai dideklarasikan. Ini adalah definisi dari enumerasi.

Martin OConnor
sumber
Seperti yang dijelaskan Thomas Paine dalam contohnya, bahasa hanya dapat diurutkan dengan sintaksis, bukan semantik. Anda berkata, item diurutkan secara logis, tetapi cara saya memahami enum, item dienkapsulasi dengan cara logis.
Bondax
2

Satu penjelasan yang mungkin adalah yang compareToharus sejalan dengan equals.

Dan equalsuntuk enum harus konsisten dengan identity equality ( ==).

Jika compareTomenjadi non-final adalah mungkin untuk menimpanya dengan perilaku yang tidak konsisten equals, yang akan sangat kontra-intuitif.

Lii
sumber
Meskipun disarankan untuk membandingkanTo () dengan sama dengan (), itu tidak perlu.
Christian H. Kuhn
-1

Jika Anda ingin mengubah urutan natural elemen enum Anda, ubah urutannya di kode sumber.

Bombe
sumber
Yup, itulah yang saya tulis di entri aslinya :)
neu242
Ya, tetapi Anda tidak benar-benar menjelaskan mengapa Anda ingin mengganti bandingkanTo (). Jadi kesimpulan saya adalah Anda mencoba melakukan Sesuatu yang Buruk ™ dan saya mencoba menunjukkan kepada Anda cara yang lebih benar.
Bombe
Saya tidak mengerti mengapa saya harus mengurutkan entri dengan tangan ketika komputer melakukannya jauh lebih baik daripada saya.
neu242
Dalam pencacahan diasumsikan bahwa Anda mengurutkan entri dengan cara tertentu karena suatu alasan. Jika tidak ada alasan lebih baik Anda menggunakan <enum> .toString (). BandingkanTo (). Atau mungkin sesuatu yang berbeda sama sekali, seperti mungkin satu Set?
Bombe
Alasan ini muncul adalah karena saya memiliki Enum dengan {isocode, countryname}. Itu diurutkan secara manual pada nama negara, yang berfungsi sebagaimana mestinya. Bagaimana jika saya memutuskan untuk mengurutkan isocode mulai sekarang?
neu242