Hampir semua kode yang memakai collection melakukan iterasi: menampilkan isi keranjang, menjumlahkan nilai siswa, atau menyaring daftar stok. Loop for bentuk enhanced (for (String item : daftar)) terlihat sederhana, tetapi di baliknya compiler javac menaruh sebuah Iterator — object kecil yang berjalan menelusuri collection sambil mengingat posisinya. Pemahaman tentang Iterator menjadi krusial begitu kita ingin mengubah isi collection di tengah penelusuran, karena di sanalah salah satu exception paling terkenal di Java muncul: ConcurrentModificationException. Pada Chapter ini kita picu exception tersebut dengan sengaja, pelajari pola benar Iterator.remove(), lalu naik kelas ke ListIterator yang mampu berjalan dua arah sekaligus mengganti dan menyisipkan elemen.
Iterator di Balik Enhanced For-Loop #
Bentuk for (T item : collection) adalah gula sintaksis. Compiler menerjemahkannya menjadi loop while yang memakai Iterator<T>: kondisi it.hasNext() memeriksa apakah masih ada elemen, dan it.next() memajukan kursor sambil mengembalikan elemen yang dilewati. Interface Iterator hanya memiliki tiga method — hasNext, next, dan remove — dan setiap implementasi collection menyediakan iterator khasnya sendiri: ArrayList menyusuri array internalnya, HashSet menyusuri bucket, TreeMap berjalan in-order di pohonnya.
Dua method pertama cukup untuk membaca. Method ketiga, remove(), adalah satu-satunya cara sah menghapus elemen dari collection yang sedang ditelusuri — dan itulah panggung utama chapter ini.
Fail-Fast dan ConcurrentModificationException #
Kita mulai dari kesalahan yang paling sering ditulis pemula: menghapus elemen lewat collection-nya sendiri (belanja.remove(item)) di tengah iterasi enhanced for-loop. Berkas berikut sengaja memuat pola tersebut:
import java.util.*;
public class HapusSaatIterasi {
public static void main(String[] args) {
List<String> belanja = new ArrayList<>(List.of("Beras", "Gula", "Kopi", "Teh"));
// Penghapusan langsung lewat List saat iterasi enhanced for-loop -> gagal.
for (String item : belanja) {
if (item.equals("Gula")) {
belanja.remove(item);
}
}
System.out.println("Baris ini tidak pernah tercetak.");
}
}JVM menghentikan program dengan exception berikut:
Exception in thread "main" java.util.ConcurrentModificationExceptionStack trace menunjuk ke ArrayList$Itr.next — iterator milik ArrayList, bukan method remove kita. Mekanismenya disebut fail-fast: setiap ArrayList menyimpan penghitung perubahan struktural bernama modCount yang naik setiap kali elemen ditambah atau dihapus langsung. Iterator mengingat salinan nilai tersebut sebagai expectedModCount; setiap next() membandingkan keduanya lebih dulu:
Ketika belanja.remove(item) menaikkan modCount tanpa sepengetahuan iterator, pemanggilan next() berikutnya mendapati kedua angka tidak sama lagi dan melempar exception. Namanya mengandung kata concurrent, tetapi kasus ini sama sekali tidak melibatkan thread ganda — satu thread saja cukup untuk memicunya. Perilaku fail-fast sesungguhnya hanya jaring pengaman yang sebaiknya melempar exception; kontraknya tidak menjamin exception pasti dilempar, jadi jangan mengandalkan exception ini sebagai alat kendali alur program.
Pola Benar dengan Iterator.remove #
Jalan yang benar adalah menghapus lewat iterator yang sedang berjalan. Method Iterator.remove() menghapus elemen yang baru saja dikembalikan next(), dan karena penghapusannya berjalan dari dalam iterator, expectedModCount ikut diperbarui sehingga penelusuran tetap konsisten. Program berikut juga memperlihatkan alternatif modern yang lebih ringkas — removeIf:
import java.util.*;
public class HapusDenganIterator {
public static void main(String[] args) {
List<String> belanja = new ArrayList<>(List.of("Beras", "Gula", "Kopi", "Teh", "Gula"));
Iterator<String> it = belanja.iterator();
while (it.hasNext()) {
String item = it.next();
if (item.equals("Gula")) {
it.remove(); // penghapusan lewat Iterator itu sendiri -> sah
}
}
System.out.println("Setelah Iterator.remove : " + belanja);
// Alternatif modern: satu panggilan removeIf dengan predicate.
List<String> stok = new ArrayList<>(List.of("Kopi", "Teh", "Gula", "Susu"));
stok.removeIf(item -> item.startsWith("T"));
System.out.println("Setelah removeIf : " + stok);
}
}Output yang Dihasilkan:
Setelah Iterator.remove : [Beras, Kopi, Teh]
Setelah removeIf : [Kopi, Gula, Susu]Poin bedahnya:
- Baris 7-13 (loop
Iteratormanual): pola tiga langkahhasNext()→next()→remove()hanya boleh memanggilremove()setelahnext()pernah dipanggil sekali untuk elemen tersebut; memanggilremove()dua kali berturut-turut melemparIllegalStateException. - Baris 9-12 (kondisi
equals("Gula")): kedua kemunculanGulaterhapus — iterator terus berjalan melewati elemen yang tidak dihapus, bukan berhenti pada yang pertama. - Baris 16-18 (
removeIf): sejak Java 8,CollectionmenyediakanremoveIf(Predicate)yang menyaring seluruh isi dalam satu panggilan; implementasi bawaannya memakai iterator juga, sehingga aman terhadap mekanisme fail-fast yang sama.
ListIterator untuk Dua Arah #
Iterator hanya maju. Untuk List — collection berurutan yang punya indeks — Java menyediakan ListIterator, turunan Iterator yang kursor miliknya bergerak di antara elemen sehingga bisa berjalan mundur, mengganti elemen (set), dan menyisipkan elemen (add). Program berikut menelusuri daftar nama ke dua arah, lalu merakit daftar baru lewat set dan add:
import java.util.*;
public class ListIteratorDuaArah {
public static void main(String[] args) {
List<String> nama = new ArrayList<>(List.of("Budi", "Siti", "Dewi"));
ListIterator<String> it = nama.listIterator();
System.out.println("Maju:");
while (it.hasNext()) {
int pos = it.nextIndex();
String s = it.next();
System.out.println(" " + pos + ": " + s);
}
System.out.println("Mundur:");
while (it.hasPrevious()) {
int pos = it.previousIndex();
String s = it.previous();
System.out.println(" " + pos + ": " + s);
}
// set mengganti elemen terakhir yang disentuh; add menyisip di posisi kursor.
it.next(); // menyentuh Budi
it.set("Budi S."); // mengganti Budi
it.add("Anton"); // menyisip Anton setelah kursor
System.out.println("Hasil akhir: " + nama);
}
}Output yang Dihasilkan:
Maju:
0: Budi
1: Siti
2: Dewi
Mundur:
2: Dewi
1: Siti
0: Budi
Hasil akhir: [Budi S., Anton, Siti, Dewi]Mari kita bedah baris-baris pentingnya:
- Baris 6 (
listIterator()): iterator dibuat dengan kursor di posisi0— tepat sebelum elemen pertama;listIterator(2)akan memulai di antara elemen kedua dan ketiga. - Baris 9-13 (loop maju):
nextIndex()melaporkan indeks elemen yang akan dikembalikannext(); setelah loop selesai, kursor berada di3, tepat setelah elemen terakhir. - Baris 16-20 (loop mundur):
hasPrevious()danprevious()mengembalikan kursor —previousIndex()melaporkan indeks elemen di kiri kursor. Urutan keluaran[Dewi, Siti, Budi]adalah kebalikan sempurna dari loop maju pada daftar yang sama. - Baris 23-26 (
setdanadd):set("Budi S.")mengganti elemen yang baru disentuhnext()— yaituBudi— tanpa menggerakkan kursor;add("Anton")menyisipkan elemen baru di posisi kursor lalu memajukan kursor melewatinya, sehingga hasil akhirnya[Budi S., Anton, Siti, Dewi].
ListIterator hanya tersedia untuk List karena hanya List yang menjamin urutan posisi; Set dan Map tidak punya indeks untuk ditelusuri mundur. Seluruh penelusuran yang kita lakukan sejauh ini membaca isi collection sebagaimana adanya — banyak kebutuhan berikutnya justru menuntut isi yang terurut: nilai siswa dari tertinggi, produk termurah lebih dulu. Urutan itu ditentukan oleh dua interface yang menjadi topik chapter berikutnya: Comparable dan Comparator.
Referensi #
- Java API Specification: java.util.Iterator
- Java API Specification: java.util.ListIterator
- Dev.java: The Java Collections Framework
Berkas Sumber
Berkas pendukung halaman ini dapat diunduh dan dijalankan langsung sesuai penjelasan di atas.
HapusDenganIterator.java
Unduh berkasimport java.util.*;
public class HapusDenganIterator {
public static void main(String[] args) {
List<String> belanja = new ArrayList<>(List.of("Beras", "Gula", "Kopi", "Teh", "Gula"));
Iterator<String> it = belanja.iterator();
while (it.hasNext()) {
String item = it.next();
if (item.equals("Gula")) {
it.remove(); // penghapusan lewat Iterator itu sendiri -> sah
}
}
System.out.println("Setelah Iterator.remove : " + belanja);
// Alternatif modern: satu panggilan removeIf dengan predicate.
List<String> stok = new ArrayList<>(List.of("Kopi", "Teh", "Gula", "Susu"));
stok.removeIf(item -> item.startsWith("T"));
System.out.println("Setelah removeIf : " + stok);
}
}
HapusSaatIterasi.java
Unduh berkasimport java.util.*;
public class HapusSaatIterasi {
public static void main(String[] args) {
List<String> belanja = new ArrayList<>(List.of("Beras", "Gula", "Kopi", "Teh"));
// Penghapusan langsung lewat List saat iterasi enhanced for-loop -> gagal.
for (String item : belanja) {
if (item.equals("Gula")) {
belanja.remove(item);
}
}
System.out.println("Baris ini tidak pernah tercetak.");
}
}
ListIteratorDuaArah.java
Unduh berkasimport java.util.*;
public class ListIteratorDuaArah {
public static void main(String[] args) {
List<String> nama = new ArrayList<>(List.of("Budi", "Siti", "Dewi"));
ListIterator<String> it = nama.listIterator();
System.out.println("Maju:");
while (it.hasNext()) {
int pos = it.nextIndex();
String s = it.next();
System.out.println(" " + pos + ": " + s);
}
System.out.println("Mundur:");
while (it.hasPrevious()) {
int pos = it.previousIndex();
String s = it.previous();
System.out.println(" " + pos + ": " + s);
}
// set mengganti elemen terakhir yang disentuh; add menyisip di posisi kursor.
it.next(); // menyentuh Budi
it.set("Budi S."); // mengganti Budi
it.add("Anton"); // menyisip Anton setelah kursor
System.out.println("Hasil akhir: " + nama);
}
}