Chapter sebelumnya menutup dengan dua method manual: Integer.valueOf(500) untuk membungkus dan .intValue() untuk membuka. Menulis keduanya setiap saat akan melelahkan — dan sejak Java 5, kita tidak perlu lagi. Compiler javac menyisipkan kedua pemanggilan itu secara otomatis lewat mekanisme autoboxing (primitive → wrapper) dan unboxing (wrapper → primitive). Fasilitas ini membuat List<Integer> terasa seperti List<int> dan aritmetika objek terasa seperti aritmetika primitive. Kenyamanan tersebut datang bersama dua jebakan serius: perbandingan == yang hasilnya bergantung pada cache, dan unboxing null yang menghentikan program saat runtime — keduanya kita uji tuntas pada Chapter ini.

Mekanisme di Balik Layar #

Ketika compiler menemui assignment Integer bonus = 50; pada konteks yang menuntut Integer, compiler menuliskan ulang baris tersebut menjadi Integer bonus = Integer.valueOf(50); — inilah autoboxing. Sebaliknya, ketika variabel Integer muncul dalam ekspresi aritmetika seperti bonus + 10, compiler menyisipkan bonus.intValue() lebih dulu — inilah unboxing. Bytecode hasil kompilasi memuat pemanggilan method tersebut secara eksplisit; kenyamanan hanya terjadi di level kode sumber.

Konversi otomatis ini berlaku di banyak konteks: assignment, argumen method, nilai kembalian, operand operasi aritmetika dan perbandingan, hingga elemen collection.

Autoboxing pada Collection #

Panggung utama autoboxing adalah collection generik. Program berikut menyimpan nilai int ke List<Integer> dan mengambilnya kembali tanpa satu pun pemanggilan valueOf/intValue yang kita tulis manual:

import java.util.ArrayList;
import java.util.List;

public class AutoboxingUnboxing {
    public static void main(String[] args) {
        List<Integer> skor = new ArrayList<>();
        skor.add(80);              // autoboxing: int -> Integer
        skor.add(90);
        skor.add(75);
        System.out.println("skor = " + skor);

        int pertama = skor.get(0); // unboxing: Integer -> int
        System.out.println("pertama = " + pertama);

        Integer jumlahBoxed = skor.get(1) + skor.get(2);  // unboxing, jumlah, boxing lagi
        System.out.println("jumlah = " + jumlahBoxed);

        Integer bonus = 50;
        int total = bonus + 10;    // unboxing bonus dalam ekspresi
        System.out.println("total  = " + total);
    }
}

Output yang Dihasilkan:

text
skor = [80, 90, 75]
pertama = 80
jumlah = 165
total  = 60

Mari kita bedah baris-baris kuncinya:

  1. Baris 6-9 (skor.add(80)): method add pada List<Integer> menerima Integer, sedangkan 80 bertipe int. Compiler menyisipkan Integer.valueOf(80) — tiga elemen tersimpan sebagai objek di heap.
  2. Baris 11 (int pertama = skor.get(0);): arah sebaliknya — get mengembalikan Integer, variabel tujuan bertipe int, compiler menyisipkan .intValue().
  3. Baris 14 (skor.get(1) + skor.get(2)): kombinasi dua arah. Operand Integer di-unboxing menjadi int, dijumlahkan sebagai int (90 + 75 = 165), lalu hasilnya di-autoboxing kembali karena variabel penampung bertipe Integer.
  4. Baris 17-18 (Integer bonus = 50; lalu bonus + 10): pembungkusan terjadi di baris pertama dan pembukaan di baris kedua — pasangan yang persis menggambarkan nama chapter ini.

Jebakan == dan Cache Integer #

Berikut eksperimen paling instruktif di seluruh Section ini. Empat baris perbandingan, dua di antaranya membingungkan:

public class JebakanPerbandingan {
    public static void main(String[] args) {
        Integer seratusA = 100;
        Integer seratusB = 100;
        System.out.println("100 == 100     : " + (seratusA == seratusB));

        Integer duaRatusA = 200;
        Integer duaRatusB = 200;
        System.out.println("200 == 200     : " + (duaRatusA == duaRatusB));

        System.out.println("200 equals 200 : " + (duaRatusA.equals(duaRatusB)));

        int duaRatusPrimitif = 200;
        System.out.println("200 == int 200 : " + (duaRatusA == duaRatusPrimitif));
    }
}

Output yang Dihasilkan:

text
100 == 100     : true
200 == 200     : false
200 equals 200 : true
200 == int 200 : true

Penjelasan masing-masing baris:

  1. Baris 4-5 (100 == 100 → true): kedua variabel di-autoboxing lewat Integer.valueOf(100). Spesifikasi Integer.valueOf mengizinkan cache untuk nilai -128 hingga 127, dan implementasi JDK memakai cache tersebut — kedua pemanggilan mengembalikan objek yang sama, sehingga perbandingan alamat == berhasil.
  2. Baris 8-9 (200 == 200 → false): nilai 200 berada di luar rentang cache, sehingga setiap autoboxing membuat objek baru di heap. Dua objek berbeda berarti dua alamat berbeda — == gagal meskipun isi nilainya sama persis.
  3. Baris 11 (equals → true): method equals() membandingkan isi objek, bukan alamat. Inilah satu-satunya cara pembandingan wrapper yang benar.
  4. Baris 13-14 (200 == int 200 → true): ketika salah satu operand bertipe primitive, compiler melakukan unboxing pada operand Integer sehingga perbandingan berlangsung antar nilai primitive — hasilnya benar secara kebetulan struktur, tetapi peraturan yang bekerja berbeda dari baris 8.

Unboxing null Melempar NullPointerException #

Wrapper boleh bernilai null; primitive tidak. Ketika compiler menyisipkan .intValue() pada variabel yang ternyata null, JVM melempar NullPointerException — dan stack trace-nya kini menyebut method yang disisipkan compiler:

public class BukaBungkusNull {
    public static void main(String[] args) {
        Integer stok = null;
        int nilai = stok;
        System.out.println(nilai);
    }
}

Program berhenti dengan java.lang.NullPointerException: Cannot invoke "java.lang.Integer.intValue()" because "<local1>" is null, menunjuk ke baris int nilai = stok;. Skenario nyatanya menyakitkan: field Integer dari database yang bernilai NULL, lalu tersangkut dalam ekspresi aritmetika — misalnya total += fieldStok. Pengecekan null sebelum unboxing adalah satu-satunya pencegahan.

Autoboxing menuntaskan rangkaian topik tipe data: primitive, reference, konversi, promosi, luapan, presisi, wrapper, hingga pembungkusan otomatis. Satu dimensi terakhir belum kita bahas secara sistematis: di mana sebuah variabel dikenal dan berapa lama variabel tersebut hidup — wilayah scope dan lifetime yang menjadi pokok Chapter penutup Section ini.

Referensi #

Berkas Sumber

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

AutoboxingUnboxing.java

Unduh berkas
java
import java.util.ArrayList;
import java.util.List;

public class AutoboxingUnboxing {
    public static void main(String[] args) {
        List<Integer> skor = new ArrayList<>();
        skor.add(80);              // autoboxing: int -> Integer
        skor.add(90);
        skor.add(75);
        System.out.println("skor = " + skor);

        int pertama = skor.get(0); // unboxing: Integer -> int
        System.out.println("pertama = " + pertama);

        Integer jumlahBoxed = skor.get(1) + skor.get(2);  // unboxing, jumlah, boxing lagi
        System.out.println("jumlah = " + jumlahBoxed);

        Integer bonus = 50;
        int total = bonus + 10;    // unboxing bonus dalam ekspresi
        System.out.println("total  = " + total);
    }
}

BukaBungkusNull.java

Unduh berkas
java
public class BukaBungkusNull {
    public static void main(String[] args) {
        Integer stok = null;
        int nilai = stok;
        System.out.println(nilai);
    }
}

JebakanPerbandingan.java

Unduh berkas
java
public class JebakanPerbandingan {
    public static void main(String[] args) {
        Integer seratusA = 100;
        Integer seratusB = 100;
        System.out.println("100 == 100     : " + (seratusA == seratusB));

        Integer duaRatusA = 200;
        Integer duaRatusB = 200;
        System.out.println("200 == 200     : " + (duaRatusA == duaRatusB));

        System.out.println("200 equals 200 : " + (duaRatusA.equals(duaRatusB)));

        int duaRatusPrimitif = 200;
        System.out.println("200 == int 200 : " + (duaRatusA == duaRatusPrimitif));
    }
}