Mengurutkan koleksi berarti menyusun ulang elemennya menurut suatu aturan pembanding, dan Java menyediakan dua jalur untuk menyatakannya, yaitu Comparable yang tertanam di kelasnya dan Comparator yang berdiri di luar kelasnya. Topik ini mempraktikkan keduanya pada koleksi objek, menuntaskan pengurutan multi-kunci, lalu menutup dengan aturan main yang membuat hasil pengurutan tetap bisa diprediksi.
Urutan alami lewat Comparable#
Kelas yang menerapkan Comparable<T> wajib menyediakan metode compareTo(T), yang mengembalikan bilangan negatif bila objek ini mendahului parameter, nol bila setara, dan positif bila mendudukinya. Pemanggil hanya membaca tanda nilai kembalian itu, bukan besarnya. Setelah kontrak itu ada, Collections.sort maupun metode sort milik list sendiri dapat dipanggil tanpa argumen tambahan.
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
public class DemoComparable {
public static void main(String[] args) {
List<Buku> rakBuku = new ArrayList<>();
rakBuku.add(new Buku("Peta Jalur Pendakian", 240));
rakBuku.add(new Buku("Panduan Merawat Sepeda", 180));
rakBuku.add(new Buku("Kamus Miniatur", 96));
System.out.println("sebelum diurutkan:");
for (Buku buku : rakBuku) {
System.out.println(" " + buku);
}
Collections.sort(rakBuku);
System.out.println("sesudah diurutkan:");
for (Buku buku : rakBuku) {
System.out.println(" " + buku);
}
}
}
class Buku implements Comparable<Buku> {
String judul;
int jumlahHalaman;
Buku(String judul, int jumlahHalaman) {
this.judul = judul;
this.jumlahHalaman = jumlahHalaman;
}
@Override
public int compareTo(Buku lain) {
return Integer.compare(this.jumlahHalaman, lain.jumlahHalaman);
}
@Override
public String toString() {
return judul + " (" + jumlahHalaman + " halaman)";
}
}sebelum diurutkan:
Peta Jalur Pendakian (240 halaman)
Panduan Merawat Sepeda (180 halaman)
Kamus Miniatur (96 halaman)
sesudah diurutkan:
Kamus Miniatur (96 halaman)
Panduan Merawat Sepeda (180 halaman)
Peta Jalur Pendakian (240 halaman)Implementasi compareTo di atas menunjukkan kebiasaan yang sebaiknya ditiru, yaitu memanggil Integer.compare alih-alih mengurangi kedua bilangan. Pola pengurangan terlihat singkat, tetapi bisa meluber (overflow) untuk selisih yang besar, sedangkan compare selalu aman. Pengurutan di sini menaik, dan bila urutan menurun yang dikehendaki, cukup balik tandanya lewat Comparator seperti pada bagian berikut.
Urutan alami String juga perlu dipahami sebelum mengandalkannya. Pembandingannya mengikuti nilai Unicode tiap karakter, sehingga semua huruf kapital mendahului huruf kecil, dan teks “Zebra” akan terurut sebelum “apel”. Pengurutan nama dengan huruf campuran karenanya sering tidak sesuai dugaan, dan butuh Comparator khusus, misalnya lewat compareToIgnoreCase, bila yang dimaksud adalah urutan alfabet sehari-hari.
Urutan luar lewat Comparator#
Comparator<T> memisahkan aturan pengurutan dari kelasnya, sehingga satu kelas bisa disusun dengan banyak cara tanpa menyentuh kodenya. Bentuk lamanya menulis metode compare manual, sedangkan bentuk modernnya merakit aturan dari lambda lewat metode pabrik Comparator.comparing beserta saudara bertipenya.
// tidak-dijalankan
Comparator<Siswa> menurutNilai = Comparator.comparingInt(s -> s.nilai);
Comparator<Siswa> menurutNilaiMenurun = menurutNilai.reversed();
Comparator<Siswa> menurutNama = Comparator.comparing(s -> s.nama);Metode pabrik itu memilih berdasarkan tipe field yang dibandingkan, misalnya comparingInt untuk int, comparingDouble untuk double, dan comparing untuk tipe yang punya urutan alami seperti String. Argumennya bisa berupa lambda, dan bila kelasnya menyediakan metode pembaca field, method reference membuat barisnya lebih ringkas lagi. Rangkaian pembanding kemudian bisa digabung lewat thenComparing, dan urutan penyambungannya menentukan urutan prioritas kuncinya.
Pengurutan multi-kunci#
Pengurutan nyata jarang bertumpu pada satu kunci. Kasus daftar siswa biasanya diurutkan menurut kelasnya lebih dahulu, lalu nilai tertinggi di dalam kelas yang sama, dan nama sebagai pemecah seri. Rangkaian thenComparing menampung kebutuhan itu dengan rapi.
import java.util.ArrayList;
import java.util.Comparator;
import java.util.List;
public class DemoComparator {
public static void main(String[] args) {
List<Siswa> daftarSiswa = new ArrayList<>();
daftarSiswa.add(new Siswa("Ali", 3, 88));
daftarSiswa.add(new Siswa("Budi", 1, 91));
daftarSiswa.add(new Siswa("Citra", 3, 79));
daftarSiswa.add(new Siswa("Dewi", 2, 91));
daftarSiswa.add(new Siswa("Eka", 1, 84));
Comparator<Siswa> urutanKelas = Comparator.comparingInt(s -> s.kelas);
Comparator<Siswa> urutanKelasNilai = urutanKelas
.thenComparing(Comparator.comparingInt((Siswa s) -> s.nilai).reversed())
.thenComparing(s -> s.nama);
daftarSiswa.sort(urutanKelasNilai);
for (Siswa siswa : daftarSiswa) {
System.out.println(siswa);
}
}
}
class Siswa {
String nama;
int kelas;
int nilai;
Siswa(String nama, int kelas, int nilai) {
this.nama = nama;
this.kelas = kelas;
this.nilai = nilai;
}
@Override
public String toString() {
return "kelas " + kelas + " | nilai " + nilai + " | " + nama;
}
}kelas 1 | nilai 91 | Budi
kelas 1 | nilai 84 | Eka
kelas 2 | nilai 91 | Dewi
kelas 3 | nilai 88 | Ali
kelas 3 | nilai 79 | CitraKeluarannya mengikuti tiga kunci secara berurutan. Kelas naik dari 1 sampai 3, nilai di dalam kelas yang sama turun karena reversed() hanya melekat pada pembanding nilai, dan nama hanya bekerja sebagai pemecah seri yang tak sempat terpakai pada data ini. Pemanggilan sort juga dilakukan langsung pada list lewat daftarSiswa.sort(...), bentuk yang kini lebih lazim daripada Collections.sort untuk list biasa.
Pengurutan di tempat lain#
sort milik list bukan satu-satunya tempat aturan pembanding ini bekerja. Kelas Arrays.sort mengurutkan array dengan aturan serupa, dan TreeSet serta TreeMap memanggil compareTo atau Comparator yang sama pada setiap penyisipan, sehingga isinya terjaga terurut tanpa pemanggilan sort sama sekali. Pengetahuan ini membuat satu implementasi pembanding bermanfaat di banyak tempat, bukan hanya di satu baris pengurutan.
Satu perbedaan penting perlu dicatat antara keduanya. Pengurutan objek, baik lewat Collections.sort, list.sort, maupun Arrays.sort untuk array objek, menjanjikan sifat stabil sebagaimana dicatat di atas. Sebaliknya, Arrays.sort untuk array primitif tidak menjanjikan kestabilan, dan itu bukan kelalaian, karena dua nilai int yang sama memang tidak bisa dibedakan satu sama lain. Akibatnya, aturan kunci kedua seperti “nilai sama diurutkan menurut nama” tidak dapat dinyatakan untuk array primitif, dan kebutuhan semacam itu diselesaikan dengan membungkus datanya dalam objek atau record.
Kesalahan umum#
- Menulis
return this.nilai - lain.nilaidi dalamcompareTo. Pengurangan bisa meluber untuk selisih yang melewati batasintdan menghasilkan tanda yang salah. GunakanInteger.compareatauDouble.compare. - Mengira
sortmenghasilkan list baru. Pengurutan terjadi di tempat, sehingga urutan lama hilang dan list aslinya yang berubah. Salin dahulu listnya bila urutan awal perlu dipertahankan. - Menempelkan
reversed()pada seluruh rantai alih-alih pada kunci yang dikehendaki.reversed()membalik pembanding yang memanggilnya, sehingga letaknya menentukan kunci mana yang ikut terbalik. - Mengurutkan
Setlangsung lewatCollections.sort. Metode itu menerimaList, karena pengurutan menuntut urutan posisi yang tidak dimilikiSet. Salin isinya keArrayListdahulu bila tampilan terurut diperlukan.
Ringkasan#
Comparablemenanamkan satu urutan alami di kelasnya melaluicompareTo, yang cukup dibaca tandanya saja.Comparatormelepas urutan ke luar kelas, dirakit lewatcomparing,comparingInt,reversed, dan dirangkaithenComparinguntuk banyak kunci.list.sort(...)danCollections.sort(...)mengurutkan di tempat dan bersifat stabil terhadap elemen yang setara.compareTosebaiknya sepakat denganequals, karena struktur terurut sepertiTreeSetmengandalkan pembandingnya semata.
Referensi#
- Object Ordering (The Java Tutorials) — Oracle
— penjelasan resmi
Comparable,Comparator, dan konsistensinya denganequals. - Interface Comparable — Oracle Java SE 21
— kontrak
compareTobeserta saran penggunaanx.compareTo(y)tanpa mengurangi manual. - Interface Comparator — Oracle Java SE 21
— daftar metode pabrik dan rangkaian
thenComparinguntuk pengurutan multi-kunci.