Variabel #
Variabel adalah nama yang diberikan ke lokasi memori tempat data disimpan. Di Java, setiap variabel wajib punya tipe yang dideklarasikan secara eksplisit sebelum bisa digunakan — inilah yang disebut statically typed. Aturan ini terasa ketat di awal, tetapi memberikan manfaat nyata: compiler mendeteksi kesalahan tipe sebelum program dijalankan, IDE bisa memberikan autocomplete yang akurat, dan kode lebih mudah dipahami karena tipe data langsung terlihat dari deklarasi. Artikel ini membahas cara mendeklarasikan dan menginisialisasi variabel, tiga kategori variabel berdasarkan lokasi deklarasinya, aturan scope dan lifetime, type inference dengan var, serta pola penggunaan yang tepat.
Deklarasi dan Inisialisasi #
Deklarasi memberi tahu compiler bahwa variabel dengan nama dan tipe tertentu akan digunakan. Inisialisasi adalah pemberian nilai pertama ke variabel tersebut. Keduanya bisa dilakukan terpisah atau sekaligus.
// Deklarasi terpisah dari inisialisasi
int jumlah; // deklarasi — belum punya nilai
jumlah = 42; // inisialisasi — diberi nilai pertama kali
// Deklarasi + inisialisasi sekaligus (cara yang lebih umum)
int harga = 15000;
double diskon = 0.1;
String nama = "Budi";
boolean aktif = true;
// Deklarasi banyak variabel bertipe sama dalam satu baris
// (boleh, tapi kurangi keterbacaan — hindari untuk variabel yang berbeda makna)
int x = 1, y = 2, z = 3;
Variabel lokal di Java tidak memiliki nilai default. Menggunakan variabel lokal sebelum diinisialisasi adalah compile error — compiler langsung menolaknya. Berbeda dengan variabel instance dan static yang mendapat nilai default otomatis (
0,false,null).public void contoh() { int nilai; System.out.println(nilai); // COMPILE ERROR: variable nilai might not have been initialized }
Tiga Kategori Variabel #
Java membagi variabel ke dalam tiga kategori berdasarkan di mana mereka dideklarasikan, yang sekaligus menentukan siapa yang memiliki data tersebut dan berapa lama data itu hidup.
flowchart TD
A[Variabel Java] --> B[Variabel Lokal]
A --> C[Variabel Instance]
A --> D[Variabel Static / Kelas]
B --> B1["Dideklarasikan di dalam metode/blok\nHanya ada selama metode berjalan\nTidak ada nilai default"]
C --> C1["Dideklarasikan di dalam kelas\ndi luar metode\nSatu salinan per objek\nAda nilai default"]
D --> D1["Dideklarasikan dengan static\nDibagikan ke semua objek\nAda selama program berjalan\nAda nilai default"]
style B fill:#3b82f6,color:#fff
style C fill:#7c3aed,color:#fff
style D fill:#059669,color:#fffVariabel Lokal #
Variabel lokal dideklarasikan di dalam metode, konstruktor, atau blok {}. Mereka hanya ada selama blok tersebut dieksekusi dan langsung dihapus dari stack saat blok selesai.
public class ContohLokal {
public double hitungLuas(double panjang, double lebar) {
// luas adalah variabel lokal — hanya ada di dalam metode ini
double luas = panjang * lebar;
return luas;
} // luas dihapus di sini
public void prosesData() {
int total = 0;
for (int i = 0; i < 10; i++) {
// i adalah variabel lokal blok for
// temp adalah variabel lokal blok for
int temp = i * 2;
total += temp;
}
// System.out.println(i); // COMPILE ERROR: i sudah di luar scope
// System.out.println(temp); // COMPILE ERROR: temp sudah di luar scope
System.out.println("Total: " + total); // total masih dalam scope
}
}
Variabel Instance #
Variabel instance dideklarasikan di dalam kelas tetapi di luar metode apapun. Setiap objek yang dibuat dari kelas tersebut memiliki salinan variabelnya sendiri — mengubah nilai di satu objek tidak mempengaruhi objek lain.
public class Produk {
// Variabel instance — satu per objek
String nama; // default: null
double harga; // default: 0.0
int stok; // default: 0
boolean tersedia; // default: false
public Produk(String nama, double harga, int stok) {
this.nama = nama; // this. membedakan field dari parameter
this.harga = harga;
this.stok = stok;
this.tersedia = stok > 0;
}
}
// Setiap objek punya salinan variabel instancenya sendiri
Produk p1 = new Produk("Laptop", 12_000_000, 5);
Produk p2 = new Produk("Mouse", 150_000, 20);
p1.harga = 11_500_000; // hanya mengubah harga milik p1
System.out.println(p1.harga); // 11500000.0
System.out.println(p2.harga); // 150000.0 — tidak berubah
Variabel Static (Variabel Kelas) #
Variabel static dideklarasikan dengan kata kunci static. Berbeda dari variabel instance, hanya ada satu salinan untuk seluruh kelas — semua objek berbagi nilai yang sama. Variabel static cocok untuk data yang memang bersifat global terhadap kelas, seperti counter, konstanta, atau konfigurasi bersama.
public class Karyawan {
// Variabel static — satu untuk seluruh kelas
static int totalKaryawan = 0;
static String namaPerusahaan = "PT Contoh";
// Variabel instance — satu per objek
String nama;
int id;
public Karyawan(String nama) {
totalKaryawan++; // setiap objek baru menambah counter bersama
this.id = totalKaryawan;
this.nama = nama;
}
}
Karyawan k1 = new Karyawan("Andi");
Karyawan k2 = new Karyawan("Budi");
Karyawan k3 = new Karyawan("Cici");
// Akses variabel static via nama kelas (bukan via objek)
System.out.println(Karyawan.totalKaryawan); // 3
System.out.println(Karyawan.namaPerusahaan); // PT Contoh
Perbandingan Ketiga Kategori #
| Aspek | Lokal | Instance | Static |
|---|---|---|---|
| Lokasi deklarasi | Di dalam metode/blok | Di dalam kelas, luar metode | Di dalam kelas dengan static |
| Kepemilikan | Tidak ada (stack frame) | Per objek | Per kelas |
| Nilai default | Tidak ada (wajib init) | Ada (0, false, null) | Ada (0, false, null) |
| Lifetime | Selama metode/blok berjalan | Selama objek hidup | Selama program berjalan |
| Cara akses | Nama langsung | this.nama atau nama | NamaKelas.nama |
Scope dan Shadowing #
Scope adalah wilayah kode di mana sebuah variabel bisa diakses. Java menggunakan block scope — variabel hanya hidup di dalam blok {} tempat ia dideklarasikan dan semua blok bersarang di dalamnya.
public class ContohScope {
int x = 10; // scope: seluruh kelas (variabel instance)
public void metode() {
int y = 20; // scope: metode ini saja
if (true) {
int z = 30; // scope: blok if ini saja
System.out.println(x); // ✓ bisa akses x
System.out.println(y); // ✓ bisa akses y
System.out.println(z); // ✓ bisa akses z
}
System.out.println(x); // ✓
System.out.println(y); // ✓
// System.out.println(z); // ✗ COMPILE ERROR: z sudah di luar scope
}
}
Variable Shadowing #
Shadowing terjadi ketika variabel lokal punya nama yang sama dengan variabel instance. Java mengizinkan ini, tetapi bisa membingungkan:
public class Akun {
String nama = "default"; // variabel instance
public void setNama(String nama) { // parameter 'nama' menyembunyikan field 'nama'
// ANTI-PATTERN: lupa this. — field tidak ter-assign, hanya parameter ke dirinya sendiri
nama = nama; // ✗ tidak melakukan apa-apa yang berguna
// BENAR: gunakan this. untuk membedakan field dari parameter
this.nama = nama; // ✓ field 'nama' di-assign dari parameter 'nama'
}
}
Type Inference dengan var
#
Sejak Java 10, kamu bisa menggunakan var sebagai pengganti tipe eksplisit untuk variabel lokal. Compiler menyimpulkan tipe dari nilai yang diberikan — disebut local variable type inference.
// Tanpa var — tipe eksplisit
ArrayList<Map<String, Integer>> data = new ArrayList<Map<String, Integer>>();
BufferedReader reader = new BufferedReader(new FileReader("file.txt"));
// Dengan var — tipe disimpulkan compiler, kode lebih ringkas
var data = new ArrayList<Map<String, Integer>>(); // tipe: ArrayList<Map<String, Integer>>
var reader = new BufferedReader(new FileReader("file.txt")); // tipe: BufferedReader
var angka = 42; // tipe: int
var nama = "Budi"; // tipe: String
var harga = 99.9; // tipe: double
var hanya bisa digunakan untuk variabel lokal yang langsung diinisialisasi — tidak bisa untuk variabel instance, parameter metode, atau return type:
// ANTI-PATTERN: var tanpa inisialisasi langsung — compiler tidak bisa menyimpulkan tipe
var x; // ✗ COMPILE ERROR
var y = null; // ✗ COMPILE ERROR — tipe null tidak bisa disimpulkan
// ANTI-PATTERN: var untuk nama variabel yang tidak menjelaskan tipe
var d = getDashboardData(); // ✗ pembaca tidak tahu tipe d tanpa melihat metode
var r = process(x); // ✗ apa tipe r?
// BENAR: var untuk tipe yang sudah jelas dari konteks
var list = new ArrayList<String>(); // ✓ jelas: ArrayList<String>
var map = new HashMap<String, Integer>(); // ✓ jelas
var i = 0; // ✓ jelas: int
// BENAR: var sangat berguna di try-with-resources
try (var conn = DriverManager.getConnection(url);
var stmt = conn.prepareStatement(sql)) {
// ...
}
flowchart TD
A{Apakah menggunakan var\nmembuat tipe masih jelas\ndari konteks kanan?} -- Ya --> B["✓ Gunakan var\nkode lebih ringkas"]
A -- Tidak --> C["✗ Tulis tipe eksplisit\nketerbacaan lebih penting"]
B --> D["var list = new ArrayList‹String›()\nvar conn = getConnection()"]
C --> E["UserRepository repo = getRepo()\nString result = process(data)"]
style B fill:#16a34a,color:#fff
style C fill:#3b82f6,color:#fffNilai Default Variabel Instance dan Static #
Variabel instance dan static yang tidak diinisialisasi secara eksplisit mendapat nilai default dari compiler. Memahami ini penting agar tidak terkejut saat membaca nilai yang tidak pernah di-set.
| Tipe | Nilai Default |
|---|---|
byte, short, int, long | 0 |
float, double | 0.0 |
char | '\u0000' (null character) |
boolean | false |
Semua tipe referensi (String, array, objek) | null |
public class ContohDefault {
int angka; // 0
double desimal; // 0.0
boolean flag; // false
String teks; // null
int[] arr; // null (bukan array kosong!)
public void cek() {
System.out.println(angka); // 0
System.out.println(desimal); // 0.0
System.out.println(flag); // false
System.out.println(teks); // null
// JANGAN: akses langsung array yang null
// System.out.println(arr.length); // ✗ NullPointerException!
// BENAR: cek null dulu
if (arr != null) {
System.out.println(arr.length);
}
}
}
Variabel final
#
Kata kunci final pada variabel berarti nilai variabel tersebut tidak bisa diubah setelah diinisialisasi pertama kali. Ini berbeda dari konstanta (yang dibahas di artikel Konstanta) — final bisa digunakan untuk variabel lokal dan parameter, bukan hanya field static.
public void proses(final String input) {
// input = "lain"; // ✗ COMPILE ERROR: parameter final tidak bisa diubah
final int batas = 100;
// batas = 200; // ✗ COMPILE ERROR
final List<String> daftar = new ArrayList<>();
daftar.add("item"); // ✓ isi list bisa diubah
// daftar = new ArrayList<>(); // ✗ referensinya yang tidak bisa diganti
}
final pada variabel lokal dan parameter adalah sinyal bagi pembaca bahwa nilai ini tidak akan berubah sepanjang scope tersebut — membantu keterbacaan dan mencegah bug dari reassignment yang tidak disengaja.
Anti-Pattern Variabel #
Beberapa kesalahan umum yang sering dilakukan saat mendeklarasikan dan menggunakan variabel di Java:
// ✗ ANTI-PATTERN 1: nama tidak deskriptif
int a = getUserCount();
String s = getCustomerName();
double d = calculateTotalPrice();
// ✓ BENAR: nama menjelaskan isi dan tujuan
int jumlahPengguna = getUserCount();
String namaPelanggan = getCustomerName();
double totalHarga = calculateTotalPrice();
// ✗ ANTI-PATTERN 2: deklarasi semua variabel di atas metode (gaya C lama)
public void prosesOrder() {
int i, j, total, diskon, harga;
String nama, alamat, kota;
// ... 30 baris kode ...
total = hitungTotal(); // pembaca harus scroll ke atas untuk lihat tipe
}
// ✓ BENAR: deklarasikan variabel sedekat mungkin dengan penggunaannya
public void prosesOrder() {
int total = hitungTotal();
int diskon = hitungDiskon(total);
String alamatPengiriman = getAlamat();
// ...
}
// ✗ ANTI-PATTERN 3: reuse variabel untuk hal yang berbeda
int hasil = hitungLuas(5, 3);
System.out.println("Luas: " + hasil);
hasil = hitungKeliling(5, 3); // 'hasil' sekarang berarti sesuatu yang berbeda
System.out.println("Keliling: " + hasil);
// ✓ BENAR: variabel berbeda untuk nilai yang berbeda
int luas = hitungLuas(5, 3);
int keliling = hitungKeliling(5, 3);
System.out.println("Luas: " + luas + ", Keliling: " + keliling);
// ✗ ANTI-PATTERN 4: akses variabel static via objek — menyesatkan
Karyawan k = new Karyawan("Andi");
System.out.println(k.totalKaryawan); // ✗ terkesan seperti milik objek k
// ✓ BENAR: akses variabel static via nama kelas
System.out.println(Karyawan.totalKaryawan); // ✓ jelas bahwa ini milik kelas
Ringkasan #
- Tiga kategori variabel — lokal (dalam metode/blok, tidak ada default), instance (per objek, ada default), static (per kelas, ada default); lokasi deklarasi menentukan kepemilikan dan lifetime.
- Variabel lokal wajib diinisialisasi — compiler menolak penggunaan variabel lokal yang belum diberi nilai; tidak ada nilai default seperti pada variabel instance.
this.untuk menghindari shadowing — gunakanthis.namaField = parameterdi konstruktor dan setter agar field kelas tidak tertimpa oleh nama parameter yang sama.varuntuk type inference — gunakanvarhanya ketika tipe sudah jelas dari sisi kanan ekspresi; hindari jika tipe tidak terlihat jelas dari konteks.finaluntuk nilai yang tidak berubah — tandai variabel lokal dan parameter denganfinaljika nilainya tidak seharusnya diubah; ini sinyal bagi pembaca dan perlindungan dari reassignment tidak sengaja.- Nilai default hanya untuk instance dan static — tipe numerik default ke
0, boolean kefalse, semua referensi kenull;nullberbeda dari array/koleksi kosong.- Deklarasikan dekat penggunaan — deklarasikan variabel sedekat mungkin dengan tempat pertama ia digunakan, bukan semua di awal metode; ini memperkecil scope mental yang harus diingat pembaca.
- Akses static via nama kelas —
NamaKelas.variabelStaticlebih jelas daripadaobjek.variabelStatickarena menunjukkan bahwa data ini milik kelas, bukan objek tertentu.