Collections Utilitas dan Imutabel

Memakai kelas Collections dan pabrik List.of, Set.of, Map.of untuk koleksi imutabel.

Daftar isi

Dua perangkat penutup modul ini sering dipakai bersamaan, yaitu kelas utilitas Collections yang menampung algoritma umum atas koleksi, dan keluarga pabrik List.of, Set.of, serta Map.of yang membuat koleksi imutabel (immutable) dalam satu baris. Topik ini mempraktikkan keduanya, membedakan koleksi yang terkunci dari koleksi yang benar-benar tak bisa berubah, lalu menutup dengan panduan memilihnya.

Kelas Collections sebagai kotak alat#

Kelas java.util.Collections berisi metode static yang bekerja terhadap koleksi, mirip peran Arrays bagi array. Isinya mencakup pengurutan, pembalikan, pertukaran, pencarian maksimum dan minimum, penghitungan frekuensi, serta pembungkus yang mengunci koleksinya. Semuanya dipanggil lewat nama kelasnya karena tidak menyimpan keadaan apa pun.

java DemoCollections.java
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

public class DemoCollections {

    public static void main(String[] args) {
        List<Integer> nilaiUjian = new ArrayList<>();
        Collections.addAll(nilaiUjian, 80, 75, 90, 60);

        System.out.println("awal      : " + nilaiUjian);
        Collections.reverse(nilaiUjian);
        System.out.println("reverse   : " + nilaiUjian);
        Collections.swap(nilaiUjian, 0, 3);
        System.out.println("swap 0,3  : " + nilaiUjian);
        Collections.sort(nilaiUjian);
        System.out.println("sort      : " + nilaiUjian);

        System.out.println("maksimum  : " + Collections.max(nilaiUjian));
        System.out.println("minimum   : " + Collections.min(nilaiUjian));
        System.out.println("frekuensi 80: " + Collections.frequency(nilaiUjian, 80));

        List<Integer> terkunci = Collections.unmodifiableList(nilaiUjian);
        try {
            terkunci.add(100);
        } catch (UnsupportedOperationException e) {
            System.out.println("Tertangkap: " + e);
        }
    }
}
text Keluaran
awal      : [80, 75, 90, 60]
reverse   : [60, 90, 75, 80]
swap 0,3  : [80, 90, 75, 60]
sort      : [60, 75, 80, 90]
maksimum  : 90
minimum   : 60
frekuensi 80: 1
Tertangkap: java.lang.UnsupportedOperationException

Baris demi barisnya menegaskan bahwa metode-metode itu mengubah koleksinya langsung di tempat, kecuali max, min, dan frequency yang hanya membaca. Baris terakhir menyisakan pertanyaan yang menarik, mengapa terkunci.add(100) melempar exception padahal terkunci merujuk list yang baru saja ditambahkan dengan lancar. Jawabannya menjadi inti bagian berikutnya.

Dua metode lain yang sering membantu adalah Collections.reverseOrder() dan Collections.nCopies(5, 0). Metode pertama memberi Comparator urutan menurun, sehingga Collections.sort(nilaiUjian, Collections.reverseOrder()) langsung mengurutkan dari nilai terbesar. Metode kedua menghasilkan list terkunci berisi lima nilai 0 untuk penyiapan data seragam. Ada pula Collections.emptyList(), emptySet(), dan emptyMap() sebagai koleksi kosong yang bisa dibagikan tanpa membuat objek baru. Ketiganya memiliki padanan pada pabrik imutabel, yaitu List.of(), Set.of(), dan Map.of(), dan pemakainya bebas memilih yang lebih terbaca.

Koleksi imutabel dari pabrik List.of, Set.of, dan Map.of#

Sejak Java 9, koleksi kecil berisi nilai tetap tidak perlu lagi dibangun satu add demi satu add. Pabrik List.of, Set.of, dan Map.of menerima elemennya sebagai argumen lalu menghasilkan koleksi yang strukturnya tidak boleh berubah, sehingga upaya menambah atau menghapus menghasilkan UnsupportedOperationException, bukan kebisuan.

java DemoImutabel.java
import java.util.List;
import java.util.Map;
import java.util.Set;

public class DemoImutabel {

    public static void main(String[] args) {
        List<String> hari = List.of("Senin", "Selasa", "Rabu");
        Set<String> warna = Set.of("merah", "hijau", "biru");
        Map<String, Integer> nilaiBenar = Map.of("soal-1", 10, "soal-2", 5);

        System.out.println("hari  : " + hari);
        System.out.println("jumlah warna : " + warna.size());
        System.out.println("ada hijau?   : " + warna.contains("hijau"));
        System.out.println("nilai soal-1 : " + nilaiBenar.get("soal-1"));
        System.out.println("jumlah pasangan: " + nilaiBenar.size());

        try {
            hari.add("Kamis");
        } catch (UnsupportedOperationException e) {
            System.out.println("Tertangkap: " + e);
        }

        try {
            Set.of("Ali", "Budi", "Ali");
        } catch (IllegalArgumentException e) {
            System.out.println("Tertangkap: " + e);
        }

        try {
            List.of("Ali", null);
        } catch (NullPointerException e) {
            System.out.println("Tertangkap: " + e);
        }
    }
}
text Keluaran
hari  : [Senin, Selasa, Rabu]
jumlah warna : 3
ada hijau?   : true
nilai soal-1 : 10
jumlah pasangan: 2
Tertangkap: java.lang.UnsupportedOperationException
Tertangkap: java.lang.IllegalArgumentException: duplicate element: Ali
Tertangkap: java.lang.NullPointerException

Program ini sengaja hanya mencetak ukuran, keanggotaan, dan pembacaan lewat kunci, bukan isi utuh Set.of dan Map.of. Alasannya perlu diingat, urutan iterasi keduanya tidak ditentukan spesifikasi dan benar-benar berubah-ubah antar peluncuran JVM, sehingga pencetakan isi utuhnya menghasilkan urutan berbeda setiap kali program dijalankan. Koleksi yang dihasilkan tetap penuh dan konsisten, hanya urutan penyusuriannya yang tidak dijanjikan.

Tiga baris Tertangkap merangkum aturan main pabrik-pabriknya. Pencobaan add menghasilkan UnsupportedOperationException karena strukturnya terkunci. Set.of yang menerima elemen kembar menolaknya dengan IllegalArgumentException pada saat pembuatan, bukan diam-diam menyaringnya. Yang paling sering mengejutkan, elemen null ditolak dengan NullPointerException, keputusan yang disengaja agar koleksi kecil ini bebas dari pemeriksaan nilai kosong.

Koleksi terkunci dan koleksi benar-benar tak berubah#

Collections.unmodifiableList dan List.of sama-sama menolak perubahan, tetapi semangatnya berbeda. unmodifiableList menghasilkan tampilan (view) yang membungkus list aslinya, sehingga perubahan pada list asal tetap terbaca lewat tampilannya, dan kuncinya tetap pada list asal yang masih bisa diubah. Sebaliknya, List.of membuat struktur baru yang tidak memiliki pintu perubahan sama sekali, tanpa list asal yang bisa disentuh.

Perbedaan itu menentukan kapan masing-masing dipakai. Tampilan terkunci cocok untuk melindungi field dari luar, misalnya Collections.unmodifiableList(new ArrayList<>(daftarMentah)) pada getter yang tidak boleh membuka koleksi internalnya. Pabrik List.of cocok untuk nilai konstan yang isinya diketahui saat menulis kode, seperti nama hari atau daftar status yang sah. Untuk salinan pertahanan (defensive copy) atas koleksi luar, List.copyOf merangkum kebiasaan itu, yaitu menghasilkan koleksi imutabel dan berhenti menyalin bila sumbernya sudah imutabel.

Keduanya juga menghemat memori dengan caranya sendiri. Tampilan terkunci tidak menyalin apa pun karena hanya membungkus koleksi asalnya, sedangkan pabrik imutabel menyimpan elemennya dalam struktur internal yang padat, jauh lebih hemat daripada ArrayList penuh slot cadangan. Bila koleksi kecil itu dipakai sebagai konstanta yang hidup sepanjang program, hematnya menumpuk, dan kepastian bahwa isinya tidak bisa berubah justru menjadi nilai tambah desainnya.

Kesalahan umum#

  • Menganggap unmodifiableList membuat list asalnya terkunci. Yang terkunci hanya tampilannya, sehingga perubahan lewat list asal tetap lolos. Bila koleksinya tidak boleh berubah sama sekali, buatlah dengan List.of atau List.copyOf.
  • Menyisipkan null ke dalam List.of atau Map.of. Pabriknya langsung melempar NullPointerException. Bersihkan nilai kosongnya dahulu atau pindahkan ke koleksi biasa.
  • Menaruh kunci kembar pada Map.of. Nilai kedua tidak menimpa yang pertama, melainkan seluruh pembuatan gagal dengan IllegalArgumentException. Susun lewat Map.ofEntries atau HashMap bila kuncinya bisa bentrok.
  • Mengira koleksi imutabel membuat isinya tak tersentuh. Yang tak berubah adalah struktur penampungnya, sehingga add dan remove ditolak, sedangkan objek di dalamnya tetap bisa dimutasi bila kelasnya memungkinkan. Gabungkan dengan record atau kelas imutabel bila isi juga harus terkunci.
  • Menyalin koleksi besar hanya untuk membacanya. unmodifiableList sudah cukup bila pemakainya hanya membaca dan koleksi asalnya dikelola dengan benar. Salinan penuh disimpan untuk kasus yang benar-benar butuh keadaan beku.

Ringkasan#

  • Kelas Collections menampung algoritma siap pakai atas koleksi, dari sort dan reverse sampai max, min, dan frequency.
  • Collections.unmodifiableList menghasilkan tampilan terkunci di atas koleksi asalnya, sedangkan List.of, Set.of, dan Map.of membuat koleksi yang strukturnya memang tanpa pintu perubahan.
  • Pabrik-pabrik itu menolak null dan, untuk Set serta Map, menolak elemen atau kunci kembar saat pembuatan.
  • Tampilan terkunci melindungi koleksi internal dari luar, sedangkan koleksi imutabel cocok untuk konstanta dan salinan pertahanan.

Referensi#