Tipe primitive pada Chapter sebelumnya menyimpan nilai mentah langsung di dalam variabel. Namun sebagian besar data di Java — teks String, tanggal, koleksi, hingga class buatan kita sendiri — bukan primitive melainkan reference type: variabelnya tidak menyimpan isi data, melainkan alamat yang menunjuk ke objek di memori heap. Perbedaan mekanik ini mengubah banyak perilaku: dua variabel dapat menunjuk objek yang sama, variabel boleh tidak menunjuk objek mana pun (null), dan operator == berhenti berarti “isinya sama”. Pada Chapter ini kita bedah mekanisme tersebut sampai ke konsekuensinya.
Primitive Menyimpan Nilai, Reference Menyimpan Alamat #
Bayangkan dua jenis laci yang berbeda. Laci primitive berisi barangnya langsung: int umur = 17; membuat laci berisi angka 17. Laci reference berisi secarik kertas bertuliskan alamat: String nama = "Budi"; membuat laci berisi alamat menuju objek String yang berada di area memori bernama heap.
Mekanismenya berjalan seperti ini:
- Ekspresi
new Titik(2, 3)memerintahkan JVM mengalokasikan memori di heap, cukup untuk satu objekTitikbeserta field-nya, lalu mengisi fieldx = 2dany = 3. - Alamat objek tersebut disimpan ke variabel
adi stack frame method yang sedang berjalan. - Ketika kita menulis
Titik b = a;, JVM menyalin alamatnya keb— bukan menyalin objeknya. Akibatnyaadanbmerujuk objek yang sama. - Ketika tidak ada variabel lagi yang menunjuk sebuah objek, garbage collector boleh memungut memori objek tersebut — kita tidak perlu membebaskannya manual seperti di C/C++.
Diagram berikut meringkas keadaan setelah Titik a = new Titik(2, 3); Titik b = a;:
stack (variabel) heap (objek)
+------+ +-------------------+
| a o-|----------------->| Titik |
+------+ | x = 2 |
| b o-|---+ | y = 3 |
+------+ | +-------------------+
+-------------------^ (objek yang sama)Dua Referensi untuk Satu Objek #
Kita buktikan perilaku tersebut dengan class sederhana Titik yang menyimpan koordinat dua dimensi:
class Titik {
int x;
int y;
Titik(int x, int y) {
this.x = x;
this.y = y;
}
void geser(int dx, int dy) {
x += dx;
y += dy;
}
@Override
public String toString() {
return "(" + x + "," + y + ")";
}
}Class di atas adalah berkas definisi — tidak memiliki method main dan tidak mencetak apa pun; class tersebut hanya menjadi bahan untuk program utama berikut:
public class DemoReferensi {
public static void main(String[] args) {
Titik a = new Titik(2, 3); // objek baru di heap, a menyimpan alamatnya
Titik b = a; // b menyalin alamat, bukan objeknya
b.geser(10, 10); // mengubah objek yang sama-sama ditunjuk
System.out.println("a = " + a);
System.out.println("b = " + b);
System.out.println("a == b : " + (a == b));
Titik c = new Titik(12, 13); // objek BARU dengan isi kebetulan sama
System.out.println("a = " + a);
System.out.println("c = " + c);
System.out.println("a == c : " + (a == c));
b = null; // putuskan rujukan b, objek tetap ada via a
System.out.println("b == null : " + (b == null));
System.out.println("a masih bisa dipakai: " + a);
}
}Output yang Dihasilkan:
a = (12,13)
b = (12,13)
a == b : true
a = (12,13)
c = (12,13)
a == c : false
b == null : true
a masih bisa dipakai: (12,13)Mari kita bedah baris-baris kuncinya:
- Baris 3 (
Titik a = new Titik(2, 3);): satu objek dibuat di heap; variabeladi stack menyimpan alamat objek tersebut. - Baris 4 (
Titik b = a;): yang tersalin kebhanyalah alamat. Tidak ada objek baru pada baris ini — hitungan objekTitikmasih satu. - Baris 5 (
b.geser(10, 10);): JVM menelusuri alamat dib, sampai ke objek yang sama dengan yang ditunjuka, lalu mengubah field-nya menjadi(12,13). Itulah sebabnya baris 6-7 mencetaka = (12,13)danb = (12,13)— keduanya melihat satu objek yang sama dari pintu berbeda. - Baris 10-12 (
Titik c = new Titik(12, 13);): objek kedua dibuat dengan isi yang kebetulan sama, tetapi alamatnya berbeda. Operator==pada reference membandingkan alamat, bukan isi — hasilnyaa == bbernilaitrue(satu objek) sedangkana == cbernilaifalse(dua objek berbeda) meskipun cetakan isinya sama. - Baris 15 (
b = null;):bberhenti menunjuk objek mana pun. Objek(12,13)tetap hidup karenaamasih menunjuk objek tersebut — memori baru akan dipungut garbage collector jika seluruh rujukan sudah putus.
Nilai null dan Nilai Bawaan Field #
Variabel reference punya satu keadaan yang tidak dimiliki primitive: null, artinya “tidak menunjuk objek mana pun”. Mengevaluasi b.x saat b bernilai null melempar NullPointerException saat runtime — error paling terkenal di dunia Java.
Perilaku null juga menjelaskan perbedaan aturan inisialisasi antara field dan variabel lokal. Field di dalam objek selalu mendapat nilai bawaan (default value) sehingga objek tidak pernah menyimpan sampah memori:
| Tipe field | Nilai bawaan |
|---|---|
byte, short, int | 0 |
long | 0L |
float | 0.0f |
double | 0.0d |
char | '\u0000' |
boolean | false |
reference (String, objek, array) | null |
Program berikut membiarkan seluruh field kosong lalu mencetak isinya:
class ProfilSiswa {
String nama; // reference type, default null
int umur; // primitive, default 0
double ipk; // primitive, default 0.0
boolean lulus; // primitive, default false
}
public class NilaiBawaan {
public static void main(String[] args) {
ProfilSiswa siswa = new ProfilSiswa(); // field belum diisi sama sekali
System.out.println("nama = " + siswa.nama);
System.out.println("umur = " + siswa.umur);
System.out.println("ipk = " + siswa.ipk);
System.out.println("lulus = " + siswa.lulus);
}
}Output yang Dihasilkan:
nama = null
umur = 0
ipk = 0.0
lulus = falseDua pengamatan dari keluaran di atas. Pertama, JVM mengisi field dengan nilai bawaan tadi secara otomatis — nama tampil sebagai null, umur sebagai 0, ipk sebagai 0.0, dan lulus sebagai false. Kedua, fasilitas nilai bawaan hanya berlaku untuk field, bukan untuk variabel lokal di dalam method; variabel lokal yang dipakai tanpa diinisialisasi ditolak compiler dengan pesan variable ... might not have been initialized — kita uji perilaku tersebut pada Chapter Deklarasi dan Inisialisasi.
String adalah Reference Type #
Tipe String yang sejak awal sering kita pakai ternyata reference type — variabel String menyimpan alamat menuju objek teks di heap. Ada dua pengecualian yang membuat String terasa istimewa: literal "Budi" disimpan di string pool agar teks yang sama dipakai bersama, dan objek String bersifat immutable — isinya tidak pernah berubah setelah dibuat; operasi seperti concat atau + menghasilkan objek baru. Karena itu membandingkan isi dua String harus memakai equals(), bukan ==.
Kini kita memegang dua kategori tipe: primitive yang menyimpan nilai mentah dan reference yang menyimpan alamat objek. Pertanyaan praktis berikutnya: bagaimana aturan lengkap mendeklarasikan variabel dari kedua kategori tersebut — deklarasi dengan atau tanpa inisialisasi, sekaligus banyak variabel dalam satu baris? Chapter Deklarasi dan Inisialisasi menyusun semua polanya.
Referensi #
Berkas Sumber
Berkas pendukung halaman ini dapat diunduh dan dijalankan langsung sesuai penjelasan di atas.
DemoReferensi.java
Unduh berkaspublic class DemoReferensi {
public static void main(String[] args) {
Titik a = new Titik(2, 3); // objek baru di heap, a menyimpan alamatnya
Titik b = a; // b menyalin alamat, bukan objeknya
b.geser(10, 10); // mengubah objek yang sama-sama ditunjuk
System.out.println("a = " + a);
System.out.println("b = " + b);
System.out.println("a == b : " + (a == b));
Titik c = new Titik(12, 13); // objek BARU dengan isi kebetulan sama
System.out.println("a = " + a);
System.out.println("c = " + c);
System.out.println("a == c : " + (a == c));
b = null; // putuskan rujukan b, objek tetap ada via a
System.out.println("b == null : " + (b == null));
System.out.println("a masih bisa dipakai: " + a);
}
}
NilaiBawaan.java
Unduh berkasclass ProfilSiswa {
String nama; // reference type, default null
int umur; // primitive, default 0
double ipk; // primitive, default 0.0
boolean lulus; // primitive, default false
}
public class NilaiBawaan {
public static void main(String[] args) {
ProfilSiswa siswa = new ProfilSiswa(); // field belum diisi sama sekali
System.out.println("nama = " + siswa.nama);
System.out.println("umur = " + siswa.umur);
System.out.println("ipk = " + siswa.ipk);
System.out.println("lulus = " + siswa.lulus);
}
}
Titik.java
Unduh berkasclass Titik {
int x;
int y;
Titik(int x, int y) {
this.x = x;
this.y = y;
}
void geser(int dx, int dy) {
x += dx;
y += dy;
}
@Override
public String toString() {
return "(" + x + "," + y + ")";
}
}