Array membantu kita menyimpan sekumpulan nilai, tetapi array punya dua keterbatasan yang cepat terasa: panjangnya tetap sejak dibuat, dan kitanya sendiri yang harus menulis logika untuk menyisipkan, menggeser, atau mencari elemen. Java Collections Framework hadir di package java.util untuk mengatasi hal tersebut dengan sekumpulan struktur data siap pakai: list yang tumbuh dinamis, set yang menolak duplikat, queue yang mengatur antrean, hingga map yang memetakan kunci ke nilai. Framework tersebut tidak sekadar kumpulan class — framework tersebut tersusun sebagai hierarki interface yang rapi, sehingga kode kita dapat bekerja pada level kontrak dan bertukar implementasi tanpa penulisan ulang. Pada Chapter ini kita memetakan hierarki tersebut sebelum menyelam ke tiap implementasi pada chapter-chapter berikutnya.

Mengapa Perlu Hierarki Interface #

Bayangkan sebuah method yang menerima daftar belanja. Jika parameter method tersebut bertipe ArrayList, method tersebut menolak LinkedList meskipun keduanya sama-sama list berurutan. Hierarki interface memecahkan masalah tersebut dengan memisahkan apa yang bisa dilakukan (kontrak) dari bagaimana dilakukan (implementasi). Sebuah variabel bertipe List<String> dapat menampung objek ArrayList, LinkedList, maupun list hasil List.of — method yang sama tetap bekerja untuk ketiganya.

Kontrak-kontrak tersebut membentuk pohon pewarisan di java.util. Akar pohonnya adalah Iterable, yang menjanjikan bahwa isinya dapat dijelajahi dengan enhanced for-loop. Satu tingkat di bawahnya, Collection menambah kontrak umum seperti add, remove, contains, size, dan isEmpty. Dari Collection lahir tiga cabang utama: List (berurutan dan berindeks), Set (elemen unik), dan Queue (antrean); Deque kemudian memperluas Queue dengan akses dua arah. Map berdiri di luar pohon tersebut karena isinya bukan sekumpulan elemen tunggal, melainkan pasangan kunci-nilai.

Perhatikan bahwa Map tidak memiliki garis ke Collection. Map memang berdiri sendiri sebagai satu keluarga interface dengan HashMap dan teman-temannya sebagai implementasi.

Kontrak Umum Interface Collection #

Semua class di bawah Collection mewarisi sekumpulan method dasar yang sama. Program berikut mendeklarasikan variabel bertipe Collection<String> untuk dua implementasi berbeda — ArrayList dan HashSet — lalu memanggil method kontrak yang sama pada keduanya:

import java.util.*;

public class KontrakCollection {
    public static void main(String[] args) {
        // Tipe variabel adalah interface, objeknya adalah implementasi.
        Collection<String> belanja = new ArrayList<>();
        belanja.add("Kopi");
        belanja.add("Teh");
        belanja.add("Susu");

        System.out.println("Jenis      : " + belanja.getClass().getSimpleName());
        System.out.println("Isi        : " + belanja);
        System.out.println("Ukuran     : " + belanja.size());
        System.out.println("Ada Teh?   : " + belanja.contains("Teh"));
        System.out.println("Kosong?    : " + belanja.isEmpty());

        belanja.remove("Teh");
        System.out.println("Setelah remove: " + belanja + " (ukuran " + belanja.size() + ")");

        // Implementasi berbeda, kontrak yang sama.
        Collection<String> unik = new HashSet<>();
        unik.add("Kopi");
        unik.add("Kopi");
        unik.add("Teh");
        System.out.println("Jenis      : " + unik.getClass().getSimpleName());
        System.out.println("Isi        : " + unik + " (ukuran " + unik.size() + ")");
    }
}

Output yang Dihasilkan:

text
Jenis      : ArrayList
Isi        : [Kopi, Teh, Susu]
Ukuran     : 3
Ada Teh?   : true
Kosong?    : false
Setelah remove: [Kopi, Susu] (ukuran 2)
Jenis      : HashSet
Isi        : [Teh, Kopi] (ukuran 2)

Mari kita bedah baris-baris pentingnya:

  1. Baris 6 (Collection<String> belanja = new ArrayList<>();): variabel dideklarasikan dengan tipe interface Collection, sedangkan objek di kanan adalah implementasi ArrayList. Compiler javac hanya mengizinkan pemanggilan method yang ada di Collection lewat variabel ini.
  2. Baris 7-9 (belanja.add(...)): method add berasal dari kontrak Collection; ArrayList menyimpan ketiga nilai termasuk duplikat jika ada.
  3. Baris 11-13 (getSimpleName, size, contains, isEmpty): getClass().getSimpleName() membuktikan objek di balik variabel bertipe interface tetaplah ArrayList; contains memeriksa keanggotaan tanpa kita menulis loop pencarian manual.
  4. Baris 16 (belanja.remove("Teh")): menghapus satu elemen berdasarkan nilai, bukan indeks — operasi yang pada array mentah harus ditulis tangan.
  5. Baris 19-23 (new HashSet<>()): implementasi diganti tanpa mengubah satu baris kode kontrak; HashSet menolak duplikat sehingga dua kali add("Kopi") hanya menyimpan satu Kopi dan size() bernilai 2.

Tiga Cabang Utama dalam Aksi #

Tiga cabang Collection memiliki karakter yang berbeda meski method dasarnya sama. Program berikut menyimpan data serupa pada List, Set, dan Queue agar perbedaan perilakunya terlihat langsung:

import java.util.*;

public class PerilakuImplementasi {
    public static void main(String[] args) {
        // List: berurutan, boleh duplikat.
        List<String> daftar = new ArrayList<>();
        daftar.add("Kopi");
        daftar.add("Teh");
        daftar.add("Kopi");
        System.out.println("List  : " + daftar);

        // Set: menolak duplikat.
        Set<String> himpunan = new HashSet<>();
        himpunan.add("Kopi");
        himpunan.add("Teh");
        himpunan.add("Kopi");
        System.out.println("Set   : " + himpunan);

        // Queue: elemen keluar sesuai urutan masuk (FIFO).
        Queue<String> antrean = new ArrayDeque<>();
        antrean.offer("Budi");
        antrean.offer("Siti");
        antrean.offer("Dewi");
        StringBuilder hasil = new StringBuilder();
        hasil.append("Queue : ");
        while (!antrean.isEmpty()) {
            hasil.append(antrean.poll()).append(" ");
        }
        System.out.println(hasil.toString().trim());
    }
}

Output yang Dihasilkan:

text
List  : [Kopi, Teh, Kopi]
Set   : [Teh, Kopi]
Queue : Budi Siti Dewi

Bedah singkatnya:

  1. List (baris 6-10): menyimpan Kopi dua kali karena List mengizinkan duplikat dan menjaga urutan penyisipan.
  2. Set (baris 13-17): duplikat Kopi kedua ditolak diam-diam, tersisa dua elemen unik.
  3. Queue (baris 20-27): offer memasukkan ke ekor antrean dan poll mengambil dari kepala, menghasilkan urutan keluar Budi Siti Dewi — sama persis dengan urutan masuk (FIFO).

Peta Interface dan Implementasi #

Tabel berikut merangkum pasangan interface-implementasi yang paling sering dipakai sehari-hari:

InterfaceKontrak intiImplementasi umum
ListBerurutan, berindeks, boleh duplikatArrayList, LinkedList
SetElemen unikHashSet, LinkedHashSet, TreeSet
QueueAntrean FIFO (atau berprioritas)ArrayDeque, LinkedList, PriorityQueue
DequeAntrean dua arah; bisa jadi stackArrayDeque, LinkedList
MapPemetaan kunci → nilai (bukan Collection)HashMap, LinkedHashMap, TreeMap

Pola umum memilih adalah mundur satu langkah dari kebutuhan data: butuh urutan dan indeks → List; butuh keunikan → Set; butuh antrean → Queue/Deque; butuh pencarian berbasis kunci → Map. Setelah kebutuhan kontrak jelas, baru pilih implementasi berdasarkan karakteristik performa — misalnya ArrayList untuk akses indeks cepat atau HashSet untuk pencarian rata-rata konstan.

Dengan peta ini di tangan, kita siap menjelajahi cabang-cabangnya satu per satu. Cabang pertama yang paling sering dipakai adalah List — kontrak berurutan dan berindeks yang akan kita bahas tuntas pada Chapter berikutnya.

Referensi #

Berkas Sumber

Berkas pendukung halaman ini dapat diunduh dan dijalankan langsung sesuai penjelasan di atas.

KontrakCollection.java

Unduh berkas
java
import java.util.*;

public class KontrakCollection {
    public static void main(String[] args) {
        // Tipe variabel adalah interface, objeknya adalah implementasi.
        Collection<String> belanja = new ArrayList<>();
        belanja.add("Kopi");
        belanja.add("Teh");
        belanja.add("Susu");

        System.out.println("Jenis      : " + belanja.getClass().getSimpleName());
        System.out.println("Isi        : " + belanja);
        System.out.println("Ukuran     : " + belanja.size());
        System.out.println("Ada Teh?   : " + belanja.contains("Teh"));
        System.out.println("Kosong?    : " + belanja.isEmpty());

        belanja.remove("Teh");
        System.out.println("Setelah remove: " + belanja + " (ukuran " + belanja.size() + ")");

        // Implementasi berbeda, kontrak yang sama.
        Collection<String> unik = new HashSet<>();
        unik.add("Kopi");
        unik.add("Kopi");
        unik.add("Teh");
        System.out.println("Jenis      : " + unik.getClass().getSimpleName());
        System.out.println("Isi        : " + unik + " (ukuran " + unik.size() + ")");
    }
}

PerilakuImplementasi.java

Unduh berkas
java
import java.util.*;

public class PerilakuImplementasi {
    public static void main(String[] args) {
        // List: berurutan, boleh duplikat.
        List<String> daftar = new ArrayList<>();
        daftar.add("Kopi");
        daftar.add("Teh");
        daftar.add("Kopi");
        System.out.println("List  : " + daftar);

        // Set: menolak duplikat.
        Set<String> himpunan = new HashSet<>();
        himpunan.add("Kopi");
        himpunan.add("Teh");
        himpunan.add("Kopi");
        System.out.println("Set   : " + himpunan);

        // Queue: elemen keluar sesuai urutan masuk (FIFO).
        Queue<String> antrean = new ArrayDeque<>();
        antrean.offer("Budi");
        antrean.offer("Siti");
        antrean.offer("Dewi");
        StringBuilder hasil = new StringBuilder();
        hasil.append("Queue : ");
        while (!antrean.isEmpty()) {
            hasil.append(antrean.poll()).append(" ");
        }
        System.out.println(hasil.toString().trim());
    }
}