Seluruh section ini kita menulis cabang dan loop dengan keyakinan bahwa eksekusi mengikuti alur yang kita rancang. Yang jarang disadari: sebelum program berjalan, compiler javac telah menelusuri alur itu sendiri — mensimulasikan setiap jalur yang mungkin dilalui eksekusi, lalu menolak program yang jalurnya bermasalah. Analisis ini disebut control flow analysis dan menaikkan dua pertanyaan yang harus terjawab pasti untuk setiap titik kode: apakah setiap variabel lokal yang dibaca pasti sudah terisi nilainya, dan apakah setiap statement mungkin dicapai oleh eksekusi. Chapter penutup section ini mengulas tuntas dua aturan tersebut — definite assignment (JLS §16) dan unreachable statement (JLS §14.22) — beserta mengapa Java membebankannya kepada compiler, bukan runtime.

Definite Assignment #

Aturan definite assignment berbunyi: variabel lokal hanya boleh dibaca bila pada setiap jalur eksekusi yang menuju titik baca itu, variabel telah diberi nilai. Perhatikan kata setiap — satu jalur bocor saja, compiler menolak. Aturan ini tegas untuk variabel lokal karena, berbeda dengan field yang otomatis terisi nilai bawaan (0, false, null), variabel lokal tidak mendapat nilai bawaan; membacanya sebelum terisi berarti membaca memori berisi sisa data tak dikenal.

Program berikut lolos analisis dan berjalan — mari kita lihat mengapa:

public class PenetapanPasti {
    public static void main(String[] args) {
        int nilai = 72;
        String predikat;

        // Kedua cabang if/else mengisi predikat: pasti terisi
        if (nilai >= 85) {
            predikat = "A";
        } else if (nilai >= 70) {
            predikat = "B";
        } else {
            predikat = "C";
        }
        System.out.println("Nilai " + nilai + " mendapat predikat " + predikat);

        // while (true) + break: compiler menganggap keluar loop mungkin
        int[] data = {4, 17, 8};
        int terbesar = data[0];
        int indeks = 1;
        while (true) {
            if (indeks >= data.length) {
                break;
            }
            if (data[indeks] > terbesar) {
                terbesar = data[indeks];
            }
            indeks++;
        }
        System.out.println("Nilai terbesar: " + terbesar);
    }
}

Output yang Dihasilkan:

text
Nilai 72 mendapat predikat B
Nilai terbesar: 17

Mari kita bedah dua pemeriksaan yang dilalui program ini:

  1. Baris 6-13 (rantai if/else if/else): compiler menelusuri tiga ujung jalur — cabang A, cabang B, dan cabang else. Setiap cabang mengisi predikat sebelum blok berakhir, sehingga pada baris 14 variabel tersebut pasti terisi tidak peduli jalur mana yang dipakai runtime: hasilnya B untuk nilai 72.
  2. Baris 20-28 (while (true) dengan break): meskipun kondisinya literal true, keberadaan break pada baris 22 membuat compiler menganggap loop dapat berakhir normal — statement setelah loop dianggap terjangkau, dan terbesar yang diinisialisasi sebelum loop sah dibaca. Loop memang berhenti lewat break ketika indeks mencapai 3, membawa jawaban 17.

Ketika Jalur Tidak Menutup #

Kini giliran kegagalan yang paling sering dijumpai. Hilangkan cabang else pada pola di atas, dan satu jalur — kondisi false — tiba di titik baca tanpa pernah mengisi variabel:

java Cabang tidak menutup semua jalur
int nilai = 50;
String predikat;

if (nilai >= 60) {
    predikat = "Lulus";
}
System.out.println("Status: " + predikat);

Compiler javac menolak dengan pesan persis (diverifikasi langsung pada JDK 25):

text
BelumPasti.java:9: error: variable predikat might not have been initialized
        System.out.println("Status: " + predikat);
                                        ^
1 error

Analisis loop lebih konservatif lagi: badan loop dianggap mungkin tidak pernah berjalan — iterasi pertama bisa saja gagal kondisi. Penugasan yang berada di dalam badan loop dengan demikian tidak menutup jalur “loop dilewati”:

java Penugasan di dalam for tidak menjamin terisi
int x;
for (int i = 0; i < 3; i++) {
    x = i;
}
System.out.println(x);

Kompilasi gagal dengan error: variable x might not have been initialized — meskipun mata kita melihat 0 < 3 pasti benar, aturan analisis memang tidak menilai isi variabel, hanya bentuk strukturnya. Bandingkan dengan do-while, yang badannya dijamin berjalan sekurang-kurangnya sekali:

public class MinimalSekali {
    public static void main(String[] args) {
        int sapaan;
        do {
            sapaan = 1;   // badan do-while dijamin berjalan minimal sekali
        } while (false);
        System.out.println("sapaan = " + sapaan);

        int hasil;
        int[] data = {5, 2, 9};
        hasil = data[0];
        for (int i = 1; i < data.length; i++) {
            if (data[i] < hasil) {
                hasil = data[i];
            }
        }
        System.out.println("nilai terkecil = " + hasil);
    }
}

Output yang Dihasilkan:

text
sapaan = 1
nilai terkecil = 2

Variabel sapaan sah dibaca karena penugasan di badan do-while pasti tereksekusi (sekali) sebelum pengujian kondisi. Adapun pencarian nilai terkecil menunjukkan pola aman untuk loop biasa: inisialisasi sebelum loop (hasil = data[0]), lalu loop hanya memperbarui — jalur “nol iterasi” tidak lagi mengancam. Pola ini mengandung satu asumsi tersirat: data[0] menuntut array tidak kosong, dan untuk data yang bisa kosong, pemeriksaan data.length == 0 pada cabang terpisah adalah langkah paling bersih.

Statement Tak Terjangkau #

Analisis kedua bekerja searah: compiler menghitung statement mana yang tidak mungkin dicapai eksekusi, dan menolaknya — bukan sekadar memperingatkan. Statement setelah return di method yang sama adalah kasus termudah:

java Statement setelah return
System.out.println("Baris pertama");
return;
System.out.println("Baris kedua");
text
TakTerjangkau.java:5: error: unreachable statement
        System.out.println("Baris kedua");
        ^
1 error

Kasus kedua menghubungkan langsung dengan loop tanpa henti: while (true) yang tidak memuat break atau return tidak pernah berakhir normal, sehingga statement sesudahnya mustahil dicapai — compiler menolak dengan error: unreachable statement pada baris setelah loop. Kita sudah melihat sisi selamatnya pada PenetapanPasti: satu break di dalam while (true) cukup membuat “keluar loop” menjadi mungkin dan kode setelahnya hidup kembali.

Mengapa Java Memeriksa Sejak Kompilasi #

Dua aturan ini adalah warisan yang disengaja. Bahasa tanpa pemeriksaan serupa (mis. C klasik) membiarkan variabel tak terisi terbaca — nilainya sampah memori, program tampak bekerja lalu berperilaku aneh di data tertentu. Java memindahkan kelas bug itu dari ranah debugging runtime yang mahal ke pesan compiler yang menunjuk baris. Pemeriksaan juga menjaga janji bahasa: kode yang lolos javac tidak akan membaca variabel lokal sampah, dan bytecode yang diverifikasi JVM memiliki alur yang sehat. Imbalannya ada: analisis ini memang konservatif (menolak beberapa program yang sebenarnya aman, seperti x di dalam for tadi), namun ketegasan itulah yang membuat pesan errornya selalu berarti.

Kita kini menutup Section Struktur Kontrol dengan bekal lengkap: percabangan if/switch klasik dan modern, empat bentuk loop, kendali break/continue polos maupun berlabel, pola loop bersarang, dan pemahaman cara compiler menilai alur yang kita tulis. Program kita mulai terasa hidup — namun masih terbiasa hidup dalam satu method raksasa. Section berikutnya, Method dan Modularitas, mengajari kita memecah alur panjang menjadi satuan-satuan kecil bernama method yang dapat diuji dan dipakai ulang.

Referensi #

Berkas Sumber

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

MinimalSekali.java

Unduh berkas
java
public class MinimalSekali {
    public static void main(String[] args) {
        int sapaan;
        do {
            sapaan = 1;   // badan do-while dijamin berjalan minimal sekali
        } while (false);
        System.out.println("sapaan = " + sapaan);

        int hasil;
        int[] data = {5, 2, 9};
        hasil = data[0];
        for (int i = 1; i < data.length; i++) {
            if (data[i] < hasil) {
                hasil = data[i];
            }
        }
        System.out.println("nilai terkecil = " + hasil);
    }
}

PenetapanPasti.java

Unduh berkas
java
public class PenetapanPasti {
    public static void main(String[] args) {
        int nilai = 72;
        String predikat;

        // Kedua cabang if/else mengisi predikat: pasti terisi
        if (nilai >= 85) {
            predikat = "A";
        } else if (nilai >= 70) {
            predikat = "B";
        } else {
            predikat = "C";
        }
        System.out.println("Nilai " + nilai + " mendapat predikat " + predikat);

        // while (true) + break: compiler menganggap keluar loop mungkin
        int[] data = {4, 17, 8};
        int terbesar = data[0];
        int indeks = 1;
        while (true) {
            if (indeks >= data.length) {
                break;
            }
            if (data[indeks] > terbesar) {
                terbesar = data[indeks];
            }
            indeks++;
        }
        System.out.println("Nilai terbesar: " + terbesar);
    }
}