Dua artikel internet bisa saling bertentangan soal perilaku satu baris kode — misalnya apa tipe hasil byte + byte. Untuk pertanyaan semacam itu, satu dokumen berkedudukan sebagai hakim terakhir: Java Language Specification (JLS). JLS adalah kontrak normatif bahasa Java yang ditulis dan dirawat perancang bahasanya, bukan tutorial ramah pembaca. Chapter ini melatih cara membacanya secara terarah: menemukan pasal yang relevan, memahami struktur dokumen, lalu membuktikan setiap klaim lewat kode kecil.

Apa Itu Java Language Specification #

JLS mendefinisikan struktur dan makna program Java: tata bahasa, konversi tipe, evaluasi ekspresi, sampai aturan eksekusi. Dokumen ini diterbitkan ulang tiap rilis JDK besar; edisi Course ini adalah JLS SE25 di docs.oracle.com/javase/specs/jls/se25/html/index.html, sementara indeks seluruh edisi beserta PDF-nya berada di docs.oracle.com/javase/specs/. Isinya terbagi menjadi 19 bab; yang paling sering kita buka antara lain bab 5 Conversions and Contexts, bab 15 Expressions, dan bab 16 Definite Assignment. Sifatnya normatif: bila compiler dan JLS berbeda pendapat, compiler yang wajib menyesuaikan.

Anatomi Pasal dan Cara Menelusuri #

Setiap aturan di JLS diberi nomor pasal bertingkat yang sekaligus menjadi jangkar alamat — sitasi §5.6 menunjuk pasal 6 dari bab 5 dan bisa ditautkan langsung di versi HTML. Cara menelusur yang efisien: buka index.html untuk melihat daftar 19 bab, buka berkas bab yang relevan (jls-5.html dan seterusnya), lalu tekan Ctrl-F dengan nomor pasal. Di dalam pasal, kotak grammar menampilkan produksi tata bahasa, contoh program diberi label bernomor seperti Example 5.0-1, dan paragraf Discussion menjelaskan motivasi di balik aturan.

Kasus Pertama: Promosi Numerik #

Pertanyaan klasik pemula: mengapa byte c = a + b; ditolak compiler javac padahal hasilnya muat di byte? Jawabannya ada di pasal 5.6 Numeric Contexts: sebelum operator biner dievaluasi, operand numerik yang lebih kecil dari int dipromosikan lebih dulu, sehingga byte + byte menghasilkan int. Compiler javac menolaknya dengan pesan incompatible types: possible lossy conversion from int to byte. Program berikut memperlihatkan aturan yang sama dari sisi yang lolos kompilasi:

public class PromosiNumerik {

    public static void main(String[] args) {
        byte a = 100;
        byte b = 28;

        int jumlah = a + b;           // byte + byte dipromosikan ke int (JLS 5.6)
        System.out.println("a + b           : " + jumlah);

        byte padat = (byte) (a + b);  // pengecoran eksplisit: 128 meluap menjadi -128
        System.out.println("(byte) (a + b)  : " + padat);

        char huruf = 'A';
        int kode = huruf + 1;         // char + int juga dipromosikan ke int (JLS 5.6)
        System.out.println("'A' + 1         : " + kode);

        huruf += 1;                   // padanan (char) (huruf + 1), pengecoran implisit (JLS 15.26.2)
        System.out.println("huruf += 1      : " + huruf);

        boolean lulus = true;
        double nilai = lulus ? 1 : 2.5; // ekspresi kondisional: 1 dipromosikan ke double (JLS 15.25)
        System.out.println("true ? 1 : 2.5  : " + nilai);
    }
}

Output yang Dihasilkan:

text
a + b           : 128
(byte) (a + b)  : -128
'A' + 1         : 66
huruf += 1      : B
true ? 1 : 2.5  : 1.0

Mari kita telaah aturan yang bekerja di baliknya:

  1. Baris 7 (int jumlah = a + b;): kedua operand byte dipromosikan ke int sebelum dijumlahkan, hasilnya 128, dan penugasan ke int sah.
  2. Baris 10 ((byte) (a + b)): pengecoran eksplisit memaksa 128 kembali ke byte; rentang byte hanya -128 sampai 127 sehingga nilai meluap menjadi -128.
  3. Baris 14 dan 17 (huruf + 1 lalu huruf += 1): char + int dipromosikan ke int sehingga hasilnya 66; += adalah operator penugasan majemuk yang oleh pasal 15.26.2 diizinkan menyertakan pengecoran implisit, jadi huruf tetap char berisi B.
  4. Baris 21 (lulus ? 1 : 2.5): pasal 15.25 menetapkan tipe ekspresi kondisional dari kedua cabangnya; karena satu cabang bertipe double, literal 1 dipromosikan dan hasilnya 1.0.

Kasus Kedua: Definite Assignment #

Pertanyaan kedua yang sering muncul: mengapa variabel lokal yang diisi di dalam blok if ditolak saat dipakai setelahnya? Pasal 16 Definite Assignment mewajibkan setiap variabel lokal pasti terisi sebelum nilainya dibaca — compiler javac menganalisis seluruh jalur eksekusi, bukan hanya jalur yang sering diambil. Berikut contoh yang sengaja dibiarkan gagal dikompilasi:

public class KuotaBelumPasti {

    public static void main(String[] args) {
        int kuota;
        if (args.length > 0) {
            kuota = Integer.parseInt(args[0]);
        }
        // Pada jalur tanpa argumen, kuota belum pasti terisi (JLS 16).
        System.out.println("Kuota awal: " + kuota);
    }
}

Eksekusi lewat java KuotaBelumPasti.java berhenti di tahap kompilasi dengan pesan persis: error: variable kuota might not have been initialized. Variabel kuota hanya terisi ketika program menerima argumen; pada jalur tanpa argumen, compiler tidak bisa membuktikan kuota pasti terisi sehingga pemakaiannya ditolak. Perbaikannya sederhana: beri nilai awal saat deklarasi (int kuota = 0;) atau pastikan semua cabang mengisi variabel itu.

JLS Bukan Satu-satunya Spesifikasi #

JLS mengawasi bahasa, tetapi tiga dokumen tetangga melengkapi peta rujukan. JVM Specification di docs.oracle.com/javase/specs/jvms/se25/html/ menjelaskan eksekusi bytecode: struktur berkas class, verifikasi, dan instruksi. Perilaku library bawaan seperti String atau Collections dicatat di Javadoc, bukan di JLS. Motivasi fitur baru didokumentasikan sebagai JEP di openjdk.org/jeps/0.

Membaca JLS memang menuntut kebiasaan — kalimatnya padat dan penuh istilah teknis. Imbalannya pasti: setiap perdebatan soal perilaku bahasa bisa diselesaikan dengan sitasi pasal, bukan dengan pendapat. Setelah bahasa, sumber yang paling sering kita buka adalah dokumentasi API; chapter berikutnya melatih cara membaca Javadoc secara efisien.

Referensi #

Berkas Sumber

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

KuotaBelumPasti.java

Unduh berkas
java
public class KuotaBelumPasti {

    public static void main(String[] args) {
        int kuota;
        if (args.length > 0) {
            kuota = Integer.parseInt(args[0]);
        }
        // Pada jalur tanpa argumen, kuota belum pasti terisi (JLS 16).
        System.out.println("Kuota awal: " + kuota);
    }
}

PromosiNumerik.java

Unduh berkas
java
public class PromosiNumerik {

    public static void main(String[] args) {
        byte a = 100;
        byte b = 28;

        int jumlah = a + b;           // byte + byte dipromosikan ke int (JLS 5.6)
        System.out.println("a + b           : " + jumlah);

        byte padat = (byte) (a + b);  // pengecoran eksplisit: 128 meluap menjadi -128
        System.out.println("(byte) (a + b)  : " + padat);

        char huruf = 'A';
        int kode = huruf + 1;         // char + int juga dipromosikan ke int (JLS 5.6)
        System.out.println("'A' + 1         : " + kode);

        huruf += 1;                   // padanan (char) (huruf + 1), pengecoran implisit (JLS 15.26.2)
        System.out.println("huruf += 1      : " + huruf);

        boolean lulus = true;
        double nilai = lulus ? 1 : 2.5; // ekspresi kondisional: 1 dipromosikan ke double (JLS 15.25)
        System.out.println("true ? 1 : 2.5  : " + nilai);
    }
}