Asumsikan Anda memiliki beberapa objek yang memiliki beberapa bidang yang dapat dibandingkan dengan:
public class Person {
private String firstName;
private String lastName;
private String age;
/* Constructors */
/* Methods */
}
Jadi dalam contoh ini, ketika Anda bertanya apakah:
a.compareTo(b) > 0
Anda mungkin bertanya apakah nama belakang a datang sebelum b, atau jika a lebih tua dari b, dll ...
Apa cara paling bersih untuk memungkinkan beberapa perbandingan antara objek-objek semacam ini tanpa menambahkan kekacauan atau overhead yang tidak perlu?
java.lang.Comparable
antarmuka memungkinkan perbandingan dengan satu bidang saja- Menambahkan berbagai metode membandingkan (yaitu
compareByFirstName()
,compareByAge()
, dll ...) adalah berantakan menurut saya.
Jadi apa cara terbaik untuk melakukan ini?
Jawaban:
Anda bisa mengimplementasikan
Comparator
yang membandingkan duaPerson
objek, dan Anda bisa memeriksa sebanyak mungkin bidang yang Anda inginkan. Anda bisa memasukkan variabel ke komparator Anda yang memberitahu bidang mana yang harus dibandingkan, meskipun mungkin lebih mudah untuk hanya menulis beberapa komparator.sumber
Dengan Java 8:
Jika Anda memiliki metode accessor:
Jika sebuah kelas mengimplementasikan Comparable maka pembanding tersebut dapat digunakan dalam metode compareTo:
sumber
(Person p)
penting untuk pembanding dirantai.Comparator
instance baru di setiap panggilan?.thenComparing(Person::getLastName, Comparator.nullsFirst(Comparator.naturalOrder()))
- pemilih bidang pertama, lalu pembandingcompareTo
seperti yang ditunjukkan di atasComparator
dibuat setiap kali metode dipanggil. Anda bisa mencegah ini dengan menyimpan komparator dalam bidang final statis pribadi.Anda harus menerapkan
Comparable <Person>
. Dengan asumsi semua bidang tidak akan menjadi nol (untuk kesederhanaan), usia itu adalah int, dan membandingkan peringkat adalah pertama, terakhir, usia,compareTo
metode ini cukup sederhana:sumber
(dari Cara untuk mengurutkan daftar objek di Jawa berdasarkan beberapa bidang )
Kode yang digunakan dalam inti ini
Menggunakan Java 8 lambda's (ditambahkan 10 April 2019)
Java 8 menyelesaikan ini dengan baik oleh lambda (meskipun Guava dan Apache Commons mungkin masih menawarkan lebih banyak fleksibilitas):
Terima kasih atas jawaban @ gaoagong di bawah ini .
Berantakan dan berbelit-belit: Menyortir dengan tangan
Ini membutuhkan banyak pengetikan, pemeliharaan, dan rawan kesalahan.
Cara reflektif: Menyortir dengan BeanComparator
Jelas ini lebih ringkas, tetapi bahkan lebih rentan kesalahan saat Anda kehilangan referensi langsung ke bidang dengan menggunakan Strings sebagai gantinya (tidak ada typesafety, auto refactoring). Sekarang jika bidang diubah namanya, kompiler bahkan tidak akan melaporkan masalah. Selain itu, karena solusi ini menggunakan refleksi, penyortiran jauh lebih lambat.
Cara ke sana: Menyortir dengan ComparisonChain Google Guava
Ini jauh lebih baik, tetapi membutuhkan beberapa kode pelat ketel untuk kasus penggunaan paling umum: nilai null harus dinilai lebih rendah secara default. Untuk bidang-nol, Anda harus memberikan arahan ekstra untuk Jambu apa yang harus dilakukan dalam kasus itu. Ini adalah mekanisme yang fleksibel jika Anda ingin melakukan sesuatu yang spesifik, tetapi sering kali Anda menginginkan case default (mis. 1, a, b, z, null).
Mengurutkan dengan Apache Commons CompareToBuilder
Seperti Guava's ComparisonChain, kelas pustaka ini mengurutkan dengan mudah pada beberapa bidang, tetapi juga mendefinisikan perilaku default untuk nilai-nilai nol (mis. 1, a, b, z, null). Namun, Anda tidak dapat menentukan hal lain juga, kecuali jika Anda menyediakan Pembanding Anda sendiri.
Jadi
Pada akhirnya itu turun ke rasa dan kebutuhan akan fleksibilitas (Guava's ComparisonChain) vs kode ringkas (Apache's CompareToBuilder).
Metode bonus
Saya menemukan solusi yang bagus yang menggabungkan beberapa komparator dengan urutan prioritas pada CodeReview di
MultiComparator
:Koleksi Apache Commons Ofcourse sudah memiliki kegunaan untuk ini:
ComparatorUtils.chainedComparator (comparatorCollection)
sumber
@ Patrick Untuk mengurutkan lebih dari satu bidang secara berurutan, coba ComparatorChain
sumber
Opsi lain yang selalu bisa Anda pertimbangkan adalah Apache Commons. Ini menyediakan banyak pilihan.
Ex:
sumber
Anda juga dapat melihat Enum yang mengimplementasikan Comparator.
http://tobega.blogspot.com/2008/05/beautiful-enums.html
misalnya
sumber
sumber
Bagi mereka yang dapat menggunakan Java 8 streaming API, ada pendekatan yang lebih rapi yang didokumentasikan dengan baik di sini: Lambdas dan pengurutan
Saya mencari yang setara dengan C # LINQ:
Saya menemukan mekanisme di Java 8 di Comparator:
Jadi di sini adalah potongan yang menunjukkan algoritme.
Lihatlah tautan di atas untuk cara yang lebih rapi dan penjelasan tentang bagaimana inferensi tipe Java membuatnya sedikit lebih kikuk untuk didefinisikan dibandingkan dengan LINQ.
Berikut ini adalah unit test lengkap untuk referensi:
sumber
Menulis secara
Comparator
manual untuk use case seperti itu adalah solusi IMO yang mengerikan. Pendekatan ad hoc semacam itu memiliki banyak kelemahan:Jadi apa solusinya?
Pertama beberapa teori.
Mari kita menyatakan proposisi "ketik
A
mendukung perbandingan" olehOrd A
. (Dari perspektif program, Anda dapat menganggapOrd A
sebagai objek yang berisi logika untuk membandingkan duaA
. Ya, sama sepertiComparator
.)Sekarang, jika
Ord A
danOrd B
, maka komposit mereka(A, B)
juga harus mendukung perbandingan. yaituOrd (A, B)
. JikaOrd A
,,Ord B
danOrd C
, laluOrd (A, B, C)
.Kami dapat memperluas argumen ini ke arity sewenang-wenang, dan mengatakan:
Ord A, Ord B, Ord C, ..., Ord Z
⇒Ord (A, B, C, .., Z)
Sebut pernyataan ini 1.
Perbandingan komposit akan berfungsi seperti yang Anda jelaskan dalam pertanyaan Anda: perbandingan pertama akan dicoba terlebih dahulu, kemudian yang berikutnya, kemudian yang berikutnya, dan seterusnya.
Itu bagian pertama dari solusi kami. Sekarang bagian kedua.
Jika Anda tahu bahwa
Ord A
, dan tahu bagaimana mengubahB
keA
(panggilan itu fungsi transformasif
), maka Anda juga dapat memilikiOrd B
. Bagaimana? Nah, ketika duaB
contoh tersebut akan dibandingkan, Anda pertama-tama mengubahnya untukA
menggunakanf
dan kemudian menerapkanOrd A
.Di sini, kami memetakan transformasi
B → A
keOrd A → Ord B
. Ini dikenal sebagai pemetaan contravarian (ataucomap
singkatnya).Ord A, (B → A)
⇒ comapOrd B
Sebut saja pernyataan ini 2.
Sekarang mari kita terapkan ini pada contoh Anda.
Anda memiliki tipe data yang bernama
Person
yang terdiri dari tiga bidang tipeString
.Kami tahu itu
Ord String
. Dengan pernyataan 1Ord (String, String, String)
,.Kita dapat dengan mudah menulis fungsi dari
Person
ke(String, String, String)
. (Cukup kembalikan tiga bidang.) Karena kita tahuOrd (String, String, String)
danPerson → (String, String, String)
, dengan pernyataan 2, kita bisa gunakancomap
untuk mendapatkannyaOrd Person
.QED.
Bagaimana saya menerapkan semua konsep ini?
Berita baiknya adalah Anda tidak harus melakukannya. Sudah ada perpustakaan yang mengimplementasikan semua ide yang dijelaskan dalam posting ini. (Jika Anda ingin tahu bagaimana ini diterapkan, Anda dapat melihat di bawah tenda .)
Ini adalah bagaimana kode akan terlihat dengannya:
Penjelasan:
stringOrd
adalah objek bertipeOrd<String>
. Ini sesuai dengan proposisi "mendukung perbandingan" asli kami.p3Ord
adalah metode yang mengambilOrd<A>
,Ord<B>
,Ord<C>
, dan kembaliOrd<P3<A, B, C>>
. Ini sesuai dengan pernyataan 1. (P3
singkatan dari produk dengan tiga elemen. Produk adalah istilah aljabar untuk komposit.)comap
berkorespondensi dengan baikcomap
,.F<A, B>
mewakili fungsi transformasiA → B
.p
adalah metode pabrik untuk membuat produk.Semoga itu bisa membantu.
sumber
Alih-alih metode perbandingan, Anda mungkin ingin mendefinisikan beberapa jenis subkelas "Pembanding" di dalam kelas Person. Dengan begitu Anda bisa meneruskannya ke metode pengurutan Koleksi standar.
sumber
Saya pikir akan lebih membingungkan jika algoritma perbandingan Anda "pintar". Saya akan menggunakan banyak metode perbandingan yang Anda sarankan.
Satu-satunya pengecualian bagi saya adalah kesetaraan. Untuk pengujian unit, ini berguna bagi saya untuk menimpa .Equals (dalam .net) untuk menentukan apakah beberapa bidang sama antara dua objek (dan bukan bahwa referensi sama).
sumber
Jika ada beberapa cara pengguna dapat memesan orang, Anda juga bisa memiliki beberapa pengaturan Pembanding sebagai konstanta di suatu tempat. Sebagian besar operasi pengurutan dan koleksi yang diurutkan menggunakan pembanding sebagai parameter.
sumber
sumber
Implementasi kode yang sama ada di sini jika kita harus mengurutkan objek Person berdasarkan beberapa bidang.
sumber
sumber
Jika Anda mengimplementasikan antarmuka Sebanding , Anda ingin memilih satu properti sederhana untuk dipesan. Ini dikenal sebagai pemesanan alami. Anggap saja sebagai default. Itu selalu digunakan ketika tidak ada komparator khusus yang disediakan. Biasanya ini adalah nama, tetapi use case Anda mungkin meminta sesuatu yang berbeda. Anda bebas menggunakan sejumlah Komparator lain yang dapat Anda suplai ke berbagai koleksi API untuk mengesampingkan pemesanan alami.
Perhatikan juga bahwa biasanya jika a.compareTo (b) == 0, maka a.equals (b) == true. Tidak apa-apa jika tidak tetapi ada efek samping yang harus diperhatikan. Lihat javadocs yang sangat baik di antarmuka Sebanding dan Anda akan menemukan banyak informasi hebat tentang ini.
sumber
Mengikuti blog diberi contoh Pembanding berantai yang baik
http://www.codejava.net/java-core/collections/sorting-a-list-by-multiple-attributes-example
Pembanding Panggilan:
sumber
Mulai dari jawaban Steve, operator ternary dapat digunakan:
sumber
Sangat mudah untuk membandingkan dua objek dengan metode kode hash di java`
sumber
Biasanya saya menimpa
compareTo()
metode saya seperti ini setiap kali saya harus melakukan penyortiran bertingkat.Di sini preferensi pertama diberikan untuk nama film kemudian ke artis dan terakhir ke songLength. Anda hanya perlu memastikan bahwa pengganda itu cukup jauh untuk tidak melewati batas satu sama lain.
sumber
Mudah dilakukan menggunakan perpustakaan Google Guava .
misalnya
Objects.equal(name, name2) && Objects.equal(age, age2) && ...
Lebih banyak contoh:
sumber