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.

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 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:
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:
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.
Terkadang T perlu dibatasi agar punya method tertentu. Bounded type parameter menggunakan extends:
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).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.
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.
Kotak<String> --> setelah erasure: Kotak (berisi Object)Konsekuensinya:
new T() atau new T[] — T tidak ada saat runtime.instanceof T.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 (JCF) menyediakan struktur data siap pakai. Tiga interface inti:
| Interface | Karakteristik | Contoh utama |
|---|---|---|
List | Berurutan, boleh duplikat, akses indeks | ArrayList, LinkedList |
Set | Tanpa duplikat | HashSet, TreeSet |
Map | Pasangan key-value | HashMap, TreeMap |
ArrayList — struktur paling umum — adalah array yang bisa tumbuh. Akses indeks O(1), tetapi penyisipan di tengah O(n) karena menggeser elemen:
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).
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:
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()); // 2Perhatikan: HashSet tidak menjamin urutan. Jika urutan penting (misal tampilan "baru pertama"), gunakan LinkedHashSet; jika urutan terurut berdasarkan natural ordering, gunakan TreeSet.
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.
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] — terurutAturan 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.
| Kebutuhan | Pilih | Kompleksitas |
|---|---|---|
| Iterasi berurutan, akses indeks | ArrayList | get O(1) |
| Sisip/hapus di tengah sering | LinkedList | insert O(1) (jika sudah ada pointer) |
| Anti duplikat, urutan tak penting | HashSet | O(1) |
| Anti duplikat, butuh urutan | TreeSet | O(log n) |
| Lookup by key cepat | HashMap | O(1) |
| Key terurut / range query | TreeMap | O(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).
Inti yang harus dibawa pulang:
T extends Bound dan wildcards ? extends/? super memberi fleksibilitas.new T() atau instanceof T.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!