Membedah class dan object secara mendalam: deklarasi class dengan field instance & static, pembuatan object dengan keyword new, cara kerja reference di Java, dan bagaimana object hidup di heap sedangkan reference tinggal di stack.

Setelah di episode 3 kalian menjalankan Hello World, sekarang saatnya membedah jantung OOP: class dan object. Di episode 2 kita mengenal keduanya secara konseptual — blueprint vs instance. Episode ini mengubah konsep itu menjadi detail teknis Java: bagaimana mendeklarasikan class, apa itu field instance dan static, bagaimana new bekerja, dan — yang sering membingungkan pemula — apa sebenarnya yang disimpan ketika kalian menulis Mobil mobilA = new Mobil().
Mengapa detail teknis ini penting? Karena banyak bug Java yang aneh berakar pada salah memahami reference. Ketika kalian melempar object ke method, yang dipindahkan adalah reference-nya, bukan salinannya. Paham konsep ini di episode 4 akan menyelamatkan kalian dari sakit kepala di episode 5, 12, dan 18.
Class adalah wadah state dan behavior. Struktur lengkapnya:
public class Mahasiswa {
String nama;
int angkatan;
static int jumlahMahasiswa;
void perkenalkan() {
System.out.println("Saya " + nama + ", angkatan " + angkatan);
}
}Tiga komponen:
nama dan angkatan adalah instance field: nilainya berbeda untuk setiap object.jumlahMahasiswa milik class, bukan object. Satu nilai dibagi oleh semua object.perkenalkan() bisa mengakses field object miliknya lewat nama langsung.Perbedaan ini fundamental:
| Instance field | Static field | |
|---|---|---|
| Dimiliki oleh | Setiap object (salinan masing-masing) | Class (dibagi semua object) |
| Diakses lewat | object.field | NamaClass.field |
| Diinisialisasi | Saat object dibuat | Saat class dimuat |
public class Main {
public static void main(String[] args) {
Mahasiswa a = new Mahasiswa();
Mahasiswa b = new Mahasiswa();
a.nama = "Alya";
b.nama = "Bima";
Mahasiswa.jumlahMahasiswa = 2;
System.out.println(a.nama); // Alya
System.out.println(b.nama); // Bima
System.out.println(Mahasiswa.jumlahMahasiswa); // 2
}
}a.nama dan b.nama berbeda, tetapi Mahasiswa.jumlahMahasiswa hanya satu — apapun object yang kalian pakai untuk mengaksesnya, nilainya sama. Inilah alasan main di episode 3 dideklarasikan static: ia milik class, bukan object tertentu.
newnewnew adalah cara Java membuat object:
Mobil mobilA = new Mobil();Apa yang terjadi dalam satu baris ini? Tiga langkah:
null untuk reference, 0 untuk angka).Setelah itu, reference ke object disimpan di variabel mobilA. Perhatikan kata "reference": mobilA bukan object itu sendiri — ia hanyalah alamat yang menunjuk ke object di heap.
Java membagi memori utama menjadi dua area:
Konsekuensi penting: saat method selesai, reference di stack hilang, tetapi object di heap tetap ada sampai garbage collector membersihkannya (saat tidak ada reference lagi). Inilah kenapa object bisa "hidup lebih lama" dari method yang menciptakannya.
Mobil mobilA = new Mobil();
mobilA.warna = "merah";
Mobil mobilB = mobilA;
mobilB.warna = "biru";
System.out.println(mobilA.warna); // biru!mobilB = mobilA tidak menyalin object — ia menyalin reference. Keduanya menunjuk ke object yang sama di heap. Mengubah lewat mobilB terlihat lewat mobilA. Ini sering mengejutkan pemula, tetapi justru menjadi kekuatan Java: object tidak disalin sembarangan, sehingga memory footprint tetap terkendali.
Karena variabel menyimpan reference, == membandingkan alamat, bukan isi:
Mobil a = new Mobil();
Mobil b = new Mobil();
System.out.println(a == b); // false, object berbeda
System.out.println(a == a); // true, reference samaUntuk membandingkan isi, kalian perlu method equals() — yang akan dibahas saat kita memahami inheritance dan Object di episode 7.
Warning
Kesalahan klasik pemula: menganggap obj1 == obj2 membandingkan isi. Di Java operator == pada object selalu membandingkan reference, dan membandingkan isi harus lewat equals(). Bug "dua object terlihat sama tapi tidak equal" hampir selalu berakar di sini.
Reference semantics juga berlaku saat object masuk ke method:
public class Main {
public static void main(String[] args) {
Mobil m = new Mobil();
m.warna = "merah";
ubahWarna(m, "hitam");
System.out.println(m.warna); // hitam
}
static void ubahWarna(Mobil mobil, String warna) {
mobil.warna = warna;
}
}Method menerima salinan reference, bukan salinan object. Karena reference menunjuk object yang sama, perubahan di dalam method terlihat dari luar. (Catatan: perilaku ini berbeda untuk tipe primitif seperti int — detailnya kita bedah di episode 5.)
Mari rangkum semua konsep dalam satu contoh:
public class Kucing {
String nama;
static int jumlahKucing;
void meong() {
System.out.println(nama + ": meong!");
}
public static void main(String[] args) {
Kucing k1 = new Kucing();
k1.nama = "Momo";
jumlahKucing++;
Kucing k2 = new Kucing();
k2.nama = "Luna";
jumlahKucing++;
k1.meong();
k2.meong();
System.out.println("Total kucing: " + jumlahKucing);
}
}Output yang diharapkan:
Momo: meong!
Luna: meong!
Total kucing: 2Perhatikan: k1.meong() dan k2.meong() memakai method yang sama, tetapi mencetak nama masing-masing. Method membaca field object yang memanggilnya — inilah wujud nyata message passing dari episode 2.
Inti yang harus dibawa pulang:
new mengalokasikan object di heap, sementara reference disimpan di stack.== membandingkan reference; equals() dibutuhkan untuk membandingkan isi.Di episode 5 selanjutnya kita akan membedah constructor & method — bagaimana object diinisialisasi, overload constructor, keyword this, varargs, dan mengapa Java melempar nilai primitif "by value" tetapi reference object "by reference". Sampai jumpa di episode 5!