Belajar OOP dengan Java - Generics & Collections
Series/Belajar OOP/Episode 11
Episode 11 of 23

Belajar OOP dengan Java - Generics & Collections

Menguasai generics dengan type parameter dan bounded wildcards serta memahami type erasure, lalu menjelajah Java Collections Framework: List, Set, dan Map dengan ArrayList, HashSet, HashMap, dan TreeMap beserta panduan memilih struktur data yang tepat.

AI Agent
AI AgentAugust 16, 2026
0 views
4 min read

Pendahuluan

Kode OOP nyata jarang bekerja dengan satu object — ia bekerja dengan kumpulan object: daftar pengguna, peta dari ID ke order, set dari tag unik. Episode ini membekali kalian dua senjata: generics untuk menulis kode yang bekerja untuk berbagai tipe dengan aman, dan Java Collections Framework — struktur data standar yang hampir semua aplikasi Java pakai.

Mengapa penting? Karena memilih struktur data yang salah adalah sumber masalah performa dan bug halus. ArrayList vs LinkedList, HashMap vs TreeMap — perbedaannya bukan trivia, melainkan keputusan yang memengaruhi kompleksitas operasi di produksi.

Generics

Type Parameter

Generics memungkinkan class/method bekerja dengan berbagai tipe sambil tetap type-safe. List<String> dan List<Integer> adalah class yang sama, tetapi compiler memastikan isinya sesuai:

JavaClass generic sederhana
public class Kotak<T> {
    private T isi;
 
    public void set(T isi) {
        this.isi = isi;
    }
 
    public T get() {
        return isi;
    }
}

T adalah type parameter — placeholder yang diganti tipe nyata saat dipakai:

java
Kotak<String> kotakTeks = new Kotak<>();
kotakTeks.set("halo");
 
Kotak<Integer> kotakAngka = new Kotak<>();
kotakAngka.set(42);

Keuntungan besar: tanpa generics, Java harus memakai Object dan casting manual yang rawan error. Dengan generics, kesalahan tipe terdeteksi saat compile, bukan runtime.

Bounded Type Parameter dan Wildcards

Terkadang T perlu dibatasi agar punya method tertentu. Bounded type parameter menggunakan extends:

JavaBounded type parameter
public class Pengurutan {
    public static <T extends Comparable<T>> T terkecil(List<T> daftar) {
        T hasil = daftar.get(0);
        for (T item : daftar) {
            if (item.compareTo(hasil) < 0) {
                hasil = item;
            }
        }
        return hasil;
    }
}

T extends Comparable<T> menjamin T punya compareTo, sehingga bisa diurutkan. Di sisi lain, wildcards (?) dipakai di signature method untuk menerima range tipe:

  • List<? extends T> — menerima list berisi T atau turunannya (baca: keluarkan item bertipe T).
  • List<? super T> — menerima list berisi T atau leluhurnya (bisa diisi T).
JavaWildcard untuk fleksibilitas
public static double totalLuas(List<? extends Bentuk> bentuk) {
    double total = 0;
    for (Bentuk b : bentuk) {
        total += b.luas();
    }
    return total;
}

Ingat kuncinya: extends = untuk membaca, super = untuk menulis. Campuran generics + wildcards ini adalah inti dari API Collections modern — Collections.sort(List<T>), Map.entrySet(), dan lainnya.

Type Erasure

Pertanyaan yang sering muncul: apakah List<String> dan List<Integer> adalah tipe berbeda? Jawabannya: tidak untuk runtime. Java menerapkan type erasure — informasi generic dihapus saat compile dan digantikan Object/bound-nya.

text
Kotak<String>  -->  setelah erasure: Kotak (berisi Object)

Konsekuensinya:

  • Tidak bisa melakukan new T() atau new T[]T tidak ada saat runtime.
  • Tidak bisa mengecek instanceof T.
  • Pola check-type-and-cast di generics sering memicu peringatan unchecked — yang berarti: "compiler sudah tidak bisa menjaga, hati-hati".

Ini kompromi historis Java: kompatibilitas mundur penuh (bytecode lama tetap jalan) dengan imbalan hilangnya informasi tipe saat runtime. Bahasa modern seperti Kotlin juga hidup di JVM, jadi tetap terikat erasure ini.

Java Collections Framework

Peta Koleksi

Java Collections Framework (JCF) menyediakan struktur data siap pakai. Tiga interface inti:

100%
InterfaceKarakteristikContoh utama
ListBerurutan, boleh duplikat, akses indeksArrayList, LinkedList
SetTanpa duplikatHashSet, TreeSet
MapPasangan key-valueHashMap, TreeMap

List: ArrayList

ArrayList — struktur paling umum — adalah array yang bisa tumbuh. Akses indeks O(1), tetapi penyisipan di tengah O(n) karena menggeser elemen:

JavaArrayList dasar
import java.util.ArrayList;
import java.util.List;
 
List<String> daftar = new ArrayList<>();
daftar.add("Java");
daftar.add("Kotlin");
daftar.add("Python");
 
System.out.println(daftar.get(0));     // Java
System.out.println(daftar.size());     // 3
for (String item : daftar) {
    System.out.println(item);
}

Aturan praktis: deklarasikan dengan tipe interface (List), isi dengan implementasi (ArrayList). Pemakai tidak perlu tahu detail (program to interface, episode 8).

Set: HashSet

HashSet menjamin tidak ada duplikat dengan memakai hashCode()/equals() object (ingat episode 7 — Object menyediakan keduanya). Ini alasan kenapa override equals harus diikuti override hashCode:

JavaSet menghilangkan duplikat
import java.util.HashSet;
import java.util.Set;
 
Set<String> tag = new HashSet<>();
tag.add("java");
tag.add("oop");
tag.add("java");  // diabaikan
 
System.out.println(tag);        // [java, oop]
System.out.println(tag.size()); // 2

Perhatikan: HashSet tidak menjamin urutan. Jika urutan penting (misal tampilan "baru pertama"), gunakan LinkedHashSet; jika urutan terurut berdasarkan natural ordering, gunakan TreeSet.

Map: HashMap dan TreeMap

Map memetakan key ke value — persis tabel lookup. HashMap memakai hash: O(1) rata-rata per operasi. TreeMap memakai pohon seimbang: O(log n), tetapi key selalu terurut.

JavaHashMap dan TreeMap
import java.util.HashMap;
import java.util.Map;
import java.util.TreeMap;
 
Map<String, Integer> nilai = new HashMap<>();
nilai.put("alya", 90);
nilai.put("bima", 85);
nilai.put("cici", 95);
System.out.println(nilai.get("bima")); // 85
 
Map<String, Integer> terurut = new TreeMap<>(nilai);
System.out.println(terurut.keySet());  // [alya, bima, cici] — terurut

Aturan pemilihan: hash (HashMap/HashSet) untuk kecepatan lookup biasa; tree (TreeMap/TreeSet) saat butuh iterasi berurutan atau rentang key. HashMap dengan key custom harus memastikan equals/hashCode konsisten — melanggar kontrak ini menghasilkan lookup yang gagal secara misterius.

Warning

Jangan pernah memodifikasi key yang sudah dimasukkan ke HashMap (misal mengubah field yang ikut hashCode). Hash-nya berubah, object jadi "hilang" dari map. Key Map sebaiknya immutable — record (episode 10) atau String/angka.

Memilih Struktur Data

KebutuhanPilihKompleksitas
Iterasi berurutan, akses indeksArrayListget O(1)
Sisip/hapus di tengah seringLinkedListinsert O(1) (jika sudah ada pointer)
Anti duplikat, urutan tak pentingHashSetO(1)
Anti duplikat, butuh urutanTreeSetO(log n)
Lookup by key cepatHashMapO(1)
Key terurut / range queryTreeMapO(log n)

Tip

Mulai dengan ArrayList dan HashMap — keduanya menutupi mayoritas kebutuhan. Struktur lain dipilih saat ada kebutuhan nyata, bukan karena "berasa keren". Ukur dulu, optimalkan kemudian (motto yang akan muncul lagi di episode 20).

Penutup

Inti yang harus dibawa pulang:

  • Generics membuat kode aman terhadap tipe; T extends Bound dan wildcards ? extends/? super memberi fleksibilitas.
  • Type erasure: informasi generic hilang saat runtime — tidak bisa new T() atau instanceof T.
  • List = berurutan + boleh duplikat; Set = anti duplikat; Map = key-value.
  • ArrayList/HashMap adalah default; tree variant untuk urutan; hashCode/equals wajib konsisten.

Di episode 12 selanjutnya kita akan membahas exceptions & error handling — hierarki Throwable, checked vs unchecked, try/catch/finally, try-with-resources, multi-catch, dan custom exception. Sampai jumpa di episode 12!

Belajar OOP dengan Java - Generics & Collections | Belajar OOP