Kita kini memiliki lebih dari selusin struktur di gudang: ArrayList dan LinkedList untuk list, tiga wajah Set, tiga wajah Map, antrean FIFO, Deque, hingga PriorityQueue. Kelimpahan itu justru menjadi masalah baru — kode produksi sering memakai ArrayList untuk segala hal karena kebiasaan, lalu membayar dengan contains yang menyisir jutaan elemen atau LinkedList yang diakses acak. Memilih collection adalah keputusan rekayasa yang seharusnya diturunkan dari pola akses data, bukan dari struktur yang paling dikenal. Pada Chapter ini kita susun kerangka berpikirnya menjadi pohon keputusan, tabel ringkas, dan satu studi kasus toko yang memakai lima struktur berbeda sekaligus secara sadar.

Mulai dari Pola Akses Data #

Cara paling cepat menentukan struktur adalah mengajukan empat pertanyaan berurutan tentang bagaimana data akan dipakai:

  1. Apakah data berupa pasangan key-value? Bila nilai selalu dicari lewat penanda tertentu (kode barang, nama siswa), keluarga Map adalah rumahnya.
  2. Apakah elemen perlu diakses berdasarkan posisi/indeks? Bila urutan penempatan bermakna dan elemen diambil menurut nomor urut, kita berbicara tentang List.
  3. Apakah elemen harus unik? Duplikat yang harus ditolak sejak masuk menunjuk ke keluarga Set.
  4. Bagaimana urutan keluarnya? Bila elemen keluar sesuai urutan kedatangan (FIFO), kebalikannya (LIFO), atau menurut prioritas, jawabannya ada di keluarga Queue/Deque.

Setiap jawaban mempersempit pilihan hingga tersisa satu atau dua kandidat, dan pilihan akhir dituntaskan oleh kebutuhan sekunder — kecepatan, urutan, atau navigasi rentang.

Pohon Keputusan #

Alur empat pertanyaan tersebut dapat digamarkan menjadi pohon keputusan yang bisa ditelusuri dari atas ke bawah:

Pohon ini memetakan kebutuhan fungsional. Setelah kandidat terpilih, pertimbangan terakhir bersifat non-fungsional — jumlah data, frekuensi operasi, dan kompleksitas tiap operasi — yang dibahas tuntas pada Chapter Performa Collection. Untuk data yang tidak akan pernah berubah setelah dibuat, tambahkan pertimbangan immutable (List.of, Map.of), dan untuk akses lintas thread, pilih varian konkuren seperti ConcurrentHashMap.

Tabel Ringkas Pemilihan #

Kebutuhan dominanPilihanAlasan singkat
Urutan kejadian, akses indeksArrayListget/set O(1), hemat memori
Sisip/hapus padat di kedua ujungArrayDequeoperasi ujung O(1) tanpa biaya indeks
Keanggotaan unik, urutan tak pentingHashSetadd/contains O(1) rata-rata
Unik + urutan penyisipanLinkedHashSetdaftar tautan mengingat urutan
Unik + terurut + navigasi rentangTreeSetpohon merah-hitam O(log n)
Pemetaan tercepat key ke valueHashMapget/put O(1) rata-rata
Pemetaan + urutan penyisipanLinkedHashMapplus mode accessOrder untuk cache LRU
Pemetaan + key terurutTreeMapfloorKey/subMap dan kawan-kawan
Antrean FIFO / tumpukan LIFOArrayDequesatu class untuk dua peran
Pengambilan menurut prioritasPriorityQueueheap O(log n) per operasi
Map yang dipakai banyak threadConcurrentHashMapkunci per-bucket, bukan per objek

Studi Kasus dalam Satu Program #

Skenarionya satu toko dengan lima kebutuhan yang berbeda. Alih-alih memakai satu ArrayList untuk semuanya, program berikut memilih struktur sesuai pola akses masing-masing data:

import java.util.*;

public class TokoSerbaBisa {
    public static void main(String[] args) {
        // Kebutuhan 1: catatan transaksi yang tampil sesuai urutan kejadian -> List.
        List<String> transaksi = new ArrayList<>();
        transaksi.add("T001 Pensil");
        transaksi.add("T002 Buku");
        transaksi.add("T003 Spidol");

        // Kebutuhan 2: pencarian stok berdasarkan kode barang -> Map.
        Map<String, Integer> stok = new HashMap<>();
        stok.put("P01", 120);
        stok.put("B02", 45);

        // Kebutuhan 3: daftar level harga unik yang terurut -> TreeSet.
        TreeSet<Integer> levelHarga = new TreeSet<>();
        levelHarga.addAll(List.of(5000, 25000, 15000, 5000));

        // Kebutuhan 4: tumpukan undo kasir -> Deque sebagai stack.
        Deque<String> undo = new ArrayDeque<>();
        undo.push("batal transaksi T003");
        undo.push("ubah harga P01");

        // Kebutuhan 5: antrean restok dengan prioritas alfabetis -> PriorityQueue.
        PriorityQueue<String> restock = new PriorityQueue<>();
        restock.add("Spidol");
        restock.add("Pensil");
        restock.add("Buku");

        System.out.println("transaksi   : " + transaksi);
        System.out.println("stok P01    : " + stok.get("P01"));
        System.out.println("level harga : " + levelHarga);
        System.out.println("undo terbaru: " + undo.peek());
        System.out.println("restock     : " + restock.poll() + ", " + restock.poll()
                + ", " + restock.poll());
    }
}

Output yang Dihasilkan:

text
transaksi   : [T001 Pensil, T002 Buku, T003 Spidol]
stok P01    : 120
level harga : [5000, 15000, 25000]
undo terbaru: ubah harga P01
restock     : Buku, Pensil, Spidol

Mari kita bedah pemilihan tiap struktur:

  1. Kebutuhan 1 — ArrayList: transaksi ditulis berurutan dan ditampilkan sesuai urutan kejadian; pola aksesnya menumpuk di ekor dan pembacaan menyeluruh, tepat untuk array dinamis.
  2. Kebutuhan 2 — HashMap: stok selalu dicari berdasarkan kode (P01), tidak pernah berdasarkan posisi; pemetaan hash menjadikan stok.get("P01") konstan berapa pun jumlah barang.
  3. Kebutuhan 3 — TreeSet: level harga harus unik (duplikat 5000 otomatis ditolak) dan tampil terurut [5000, 15000, 25000] — dua kebutuhan dalam satu struktur tanpa penyortiran manual.
  4. Kebutuhan 4 — ArrayDeque sebagai stack: undo memakai push/peek; operasi undo selalu mengambil aksi terbaru, pola LIFO yang dilayani di kedua ujung deque.
  5. Kebutuhan 5 — PriorityQueue: urutan restok bukan urutan masuk melainkan prioritas (di sini alfabetis, di produksi biasanya tingkat kehabisan); poll mengeluarkan Buku lebih dulu meski Spidol masuk paling awal.

Kesalahan Umum Pemilihan #

Beberapa pola keliru muncul berulang di kode nyata. Pertama, memakai List.contains sebagai pemeriksaan keanggotaan untuk data besar — tiap panggilan menyisir seluruh isi dengan O(n); bila pertanyaannya hanya “apakah sudah ada?”, HashSet menjawab dalam O(1) rata-rata. Kedua, memakai LinkedList karena “terkenal cepat menyisip” padahal aksesnya acak — menyisip di tengah LinkedList tetap menuntut penelusuran O(n) untuk mencapai posisi. Ketiga, mengabaikan urutan lalu kaget saat keluaran HashMap berubah — bila urutan punya makna bisnis, nyatakan sejak awal lewat LinkedHashMap atau TreeMap. Keempat, memilih Vector atau Hashtable di kode baru karena meniru contoh lama — keduanya warisan era Java 1.0 yang digantikan struktur modern.

Kerangka “pola akses menentukan struktur” melengkapi seluruh Section. Satu dimensi terakhir yang belum kita ukur: berapa biaya sebenarnya dari tiap operasi pada tiap struktur — dan mengapa ArrayList bisa “cukup cepat” meski teori menyebut penyisipan di tengah berbiaya O(n). Chapter penutup menjawabnya dengan tabel kompleksitas dan penjelasan struktural di balik setiap angka.

Referensi #

Berkas Sumber

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

TokoSerbaBisa.java

Unduh berkas
java
import java.util.*;

public class TokoSerbaBisa {
    public static void main(String[] args) {
        // Kebutuhan 1: catatan transaksi yang tampil sesuai urutan kejadian -> List.
        List<String> transaksi = new ArrayList<>();
        transaksi.add("T001 Pensil");
        transaksi.add("T002 Buku");
        transaksi.add("T003 Spidol");

        // Kebutuhan 2: pencarian stok berdasarkan kode barang -> Map.
        Map<String, Integer> stok = new HashMap<>();
        stok.put("P01", 120);
        stok.put("B02", 45);

        // Kebutuhan 3: daftar level harga unik yang terurut -> TreeSet.
        TreeSet<Integer> levelHarga = new TreeSet<>();
        levelHarga.addAll(List.of(5000, 25000, 15000, 5000));

        // Kebutuhan 4: tumpukan undo kasir -> Deque sebagai stack.
        Deque<String> undo = new ArrayDeque<>();
        undo.push("batal transaksi T003");
        undo.push("ubah harga P01");

        // Kebutuhan 5: antrean restok dengan prioritas alfabetis -> PriorityQueue.
        PriorityQueue<String> restock = new PriorityQueue<>();
        restock.add("Spidol");
        restock.add("Pensil");
        restock.add("Buku");

        System.out.println("transaksi   : " + transaksi);
        System.out.println("stok P01    : " + stok.get("P01"));
        System.out.println("level harga : " + levelHarga);
        System.out.println("undo terbaru: " + undo.peek());
        System.out.println("restock     : " + restock.poll() + ", " + restock.poll()
                + ", " + restock.poll());
    }
}