Tampilan
Kenapa penting
Di aplikasi backend, sebagian nilai berlaku untuk seluruh aplikasi — batas maksimum item per pesanan, tarif ongkir flat, format nomor invoice — sementara sebagian lain milik satu object saja, seperti isi keranjang seorang pengguna. Salah menempatkan data di level class (static) bisa membuat data pengguna bocor ke pengguna lain saat server melayani ribuan request bersamaan. Sebaliknya, object yang tidak bisa berubah (immutable) — seperti nominal uang yang sudah dihitung — membuat kode lebih aman dan mudah ditebak. Bab ini membahas tiga alat untuk mengatur itu: static, final, dan immutability.
Konsep inti
Anggota instance vs anggota static
Field dan method yang kamu tulis sejauh ini adalah anggota instance: setiap object punya salinan field sendiri, dan method-nya bekerja dengan field object itu. Anggota yang diberi kata kunci static adalah milik class: hanya ada satu, dipakai bersama semua object, dan diakses lewat nama class.
java
class Product {
static int createdCount = 0; // milik class: satu untuk semua object
private final String name; // milik object: tiap object punya sendiri
Product(String name) {
this.name = name;
createdCount++;
}
String name() {
return name;
}
static String normalizeSku(String raw) { // tidak butuh field object
return raw.strip().toUpperCase();
}
}
public class StaticDemo {
public static void main(String[] args) {
Product coffee = new Product("Kopi");
Product tea = new Product("Teh");
System.out.println(coffee.name() + ", " + tea.name());
System.out.println(Product.createdCount);
System.out.println(Product.normalizeSku(" kp-001 "));
}
}Output:
text
Kopi, Teh
2
KP-001Dua object Product punya name masing-masing, tetapi createdCount hanya satu dan terus bertambah. Method normalizeSku tidak menyentuh field object mana pun, jadi cocok dijadikan static dan dipanggil lewat nama class. Sekarang kamu juga paham kenapa main harus static: JVM memanggilnya sebelum ada object apa pun.
Karena tidak terikat ke object, method static tidak bisa membaca field instance secara langsung:
text
StaticError.java:5: error: non-static variable itemCount cannot be referenced from a static context
return itemCount == 0;
^
1 errorKapan static tepat dipakai
Cocok sebagai static | Contoh |
|---|---|
| Method utilitas yang hanya bergantung pada parameternya | Math.max(a, b), Integer.parseInt(s), normalizeSku |
| Konstanta yang berlaku untuk seluruh aplikasi | Integer.MAX_VALUE, MAX_ITEMS_PER_ORDER |
| Static factory method yang membuat object | String.valueOf(42), Weight.ofGrams(500) (lihat bawah) |
| Penghitung/cache bersama — hati-hati, lihat Jebakan umum | createdCount di atas (hanya untuk contoh) |
Aturan praktis: kalau method butuh data milik object, jadikan method instance. Kalau tidak, static boleh.
Konstanta static final
Angka "ajaib" yang tersebar di kode — 50, 200000, 15000 — sulit dipahami dan rawan tidak konsisten. Beri nama dengan konstanta: field static final yang ditulis UPPER_SNAKE_CASE.
java
public class FinalDemo {
static final int MAX_ITEMS_PER_ORDER = 50;
public static void main(String[] args) {
final int discount = 10;
// discount = 20; // error: cannot assign a value to final variable discount
final int[] stock = {5, 3};
stock[0] = 7; // boleh: yang final referensinya, bukan isi array
// stock = new int[] {1}; // error: referensinya tidak boleh diganti
final StringBuilder log = new StringBuilder("mulai");
log.append(" → selesai"); // object-nya tetap bisa diubah
System.out.println(MAX_ITEMS_PER_ORDER + " " + discount);
System.out.println(stock[0] + " " + log);
}
}Output:
text
50 10
7 mulai → selesaiArti final
final berarti "hanya boleh di-assign sekali". Artinya sedikit berbeda tergantung tempatnya:
- Variabel lokal & parameter — tidak bisa di-assign ulang. Mencobanya menghasilkan
error: cannot assign a value to final variable discount. - Field — wajib diisi tepat sekali, biasanya di constructor, lalu tidak bisa diganti. Ini fondasi immutability.
- Class —
final classtidak bisa diturunkan (inheritance dibahas di bab berikutnya).Stringadalah contohnya.
Perhatikan contoh di atas: final hanya mengunci referensi, bukan isi object yang ditunjuk. Array stock dan StringBuilder log tetap bisa diubah isinya. Jadi final tidak otomatis berarti immutable.
Immutability dasar
Di M01 kamu belajar String itu immutable: toUpperCase() mengembalikan String baru. Kamu bisa membuat class sendiri dengan sifat yang sama. Resepnya:
- Semua field
private final. - Tidak ada setter atau method lain yang mengubah field.
- Operasi yang "mengubah" mengembalikan object baru.
- Class diberi
finalagar tidak ada turunan yang menambahkan perilaku yang bisa berubah.
java
final class Weight {
private final int grams;
private Weight(int grams) {
this.grams = grams;
}
static Weight ofGrams(int grams) {
if (grams < 0) {
throw new IllegalArgumentException("Berat tidak boleh negatif: " + grams);
}
return new Weight(grams);
}
static Weight ofKilograms(int kilograms) {
return ofGrams(kilograms * 1000);
}
int grams() {
return grams;
}
Weight plus(Weight other) {
return new Weight(grams + other.grams); // object baru, object lama tidak berubah
}
@Override
public String toString() {
return grams + " g";
}
}
public class WeightDemo {
public static void main(String[] args) {
Weight box = Weight.ofGrams(500);
Weight total = box.plus(Weight.ofKilograms(2));
box.plus(Weight.ofGrams(250)); // hasilnya dibuang — box tetap 500 g
System.out.println(box);
System.out.println(total);
}
}Output: 500 g lalu 2500 g. Karena Weight tidak pernah berubah, object yang sama aman dioper ke mana saja — tidak ada method lain yang bisa mengubahnya diam-diam. (Anotasi @Override pada toString dibahas di bab berikutnya; throw new IllegalArgumentException adalah cara menolak input yang tidak valid, dan detail exception ada di M03.)
Static factory method
Perhatikan constructor Weight dibuat private, dan object dibuat lewat method static bernama ofGrams dan ofKilograms. Pola ini disebut static factory method. Kelebihannya dibanding constructor:
- Bernama —
Weight.ofKilograms(2)jelas satuannya;new Weight(2)tidak. - Satu pintu validasi — semua object pasti lewat pemeriksaan yang sama.
- Bebas memilih object yang dikembalikan — misalnya mengembalikan konstanta yang sudah ada alih-alih membuat baru.
Pola ini ada di mana-mana di JDK: Integer.valueOf, String.valueOf, dan nanti List.of di M03.
Contoh di dunia backend
Layanan checkout biasanya punya aturan bisnis global (konstanta) dan ringkasan pesanan yang tidak boleh berubah setelah dihitung. Contoh berikut menggabungkan class utilitas berisi konstanta dengan object ringkasan yang immutable:
java
final class ShippingRules {
static final long FREE_SHIPPING_MIN = 200_000;
static final long FLAT_RATE = 15_000;
private ShippingRules() {
// class utilitas: tidak untuk dibuat object-nya
}
static long shippingFee(long subtotal) {
return subtotal >= FREE_SHIPPING_MIN ? 0 : FLAT_RATE;
}
}
final class OrderSummary {
private final String orderId;
private final long subtotal;
private final long shipping;
private OrderSummary(String orderId, long subtotal, long shipping) {
this.orderId = orderId;
this.subtotal = subtotal;
this.shipping = shipping;
}
static OrderSummary of(String orderId, long subtotal) {
if (subtotal < 0) {
throw new IllegalArgumentException("Subtotal negatif: " + subtotal);
}
return new OrderSummary(orderId, subtotal, ShippingRules.shippingFee(subtotal));
}
OrderSummary withSubtotal(long newSubtotal) {
return of(orderId, newSubtotal); // "mengubah" = membuat object baru
}
long total() {
return subtotal + shipping;
}
@Override
public String toString() {
return orderId + " subtotal=" + subtotal + " ongkir=" + shipping + " total=" + total();
}
}
public class Checkout {
public static void main(String[] args) {
OrderSummary draft = OrderSummary.of("INV-001", 185_000);
OrderSummary updated = draft.withSubtotal(210_000);
System.out.println(draft);
System.out.println(updated);
}
}Output:
text
INV-001 subtotal=185000 ongkir=15000 total=200000
INV-001 subtotal=210000 ongkir=0 total=210000Constructor private di ShippingRules mencegah orang membuat object dari class yang memang hanya berisi konstanta dan method static. Method withSubtotal mengikuti konvensi withXxx yang lazim untuk object immutable: ringkasan lama (draft) tetap utuh, misalnya untuk dicatat di log audit, sedangkan ringkasan baru dihitung ulang lengkap dengan ongkirnya.
Sekarang bandingkan dengan pola yang berbahaya — menyimpan data per-request di field static:
java
// Potongan: JANGAN ditiru
class CartService {
static String currentUserId; // dibagi SEMUA request yang sedang berjalan
void handleRequest(String userId) {
currentUserId = userId;
// ... beberapa milidetik kemudian request lain menimpa currentUserId ...
loadCart(currentUserId); // bisa memuat keranjang milik pengguna lain!
}
void loadCart(String userId) {
// ambil data keranjang dari database
}
}Server web memproses banyak request bersamaan dengan object service yang sama. Field static (dan juga field instance pada service yang dipakai bersama) adalah satu tempat yang ditulis semua request, sehingga nilainya bisa tertimpa. Data milik satu request harus mengalir lewat parameter dan variabel lokal.
Pertanyaan interview
1. Apa beda method static dan method instance? Kapan memakai static?
Method instance bekerja dengan data milik satu object dan dipanggil lewat object itu. Method static milik class, dipanggil lewat nama class, dan tidak bisa mengakses field instance secara langsung. Pakai static untuk utilitas yang hanya bergantung pada parameter, konstanta, dan static factory method.
2. Apakah final membuat object menjadi immutable?
Tidak. final hanya mencegah variabel atau field di-assign ulang — object yang ditunjuk tetap bisa diubah bila class-nya mutable (misalnya array atau StringBuilder). Object immutable butuh field private final, tanpa setter, operasi yang mengembalikan object baru, dan class yang tidak bisa diturunkan.
3. Kenapa object immutable disukai di aplikasi backend?
Nilainya tidak bisa berubah setelah dibuat dan divalidasi, jadi aman dioper ke banyak method dan dipakai banyak thread sekaligus tanpa sinkronisasi. Kode juga lebih mudah ditebak: tidak ada efek samping tersembunyi. Contoh di JDK: String, Integer, dan class tanggal-waktu LocalDate.
Jebakan umum
- Menyimpan data per-request di field
static. Semua request berbagi field itu sehingga data bisa tertukar antarpengguna. Pakai parameter dan variabel lokal. - Mengira
final= immutable.final int[]danfinal StringBuildertetap bisa diubah isinya. - Membuang hasil operasi immutable.
box.plus(...)tanpa disimpan tidak mengubah apa pun — tulisbox = box.plus(...). - Konstanta tanpa
final.public static int MAX_ITEMS = 50;bisa diubah dari mana saja; selalustatic final. - Memanggil method instance dari
maintanpa membuat object → errornon-static ... cannot be referenced from a static context. Buat object dulu dengannew, atau jadikan method itustaticbila memang tidak butuh field.
Ringkasan
- Anggota
staticmilik class (satu untuk semua object); anggota instance milik tiap object. - Method
staticcocok untuk utilitas, konstanta, dan static factory method — bukan untuk menyimpan data per-request. - Konstanta ditulis
static finaldengan namaUPPER_SNAKE_CASE. finalmengunci assignment, bukan isi object;final classtidak bisa diturunkan.- Class immutable: field
private final, tanpa setter, operasi mengembalikan object baru, classfinal. - Static factory method (
of,ofGrams) memberi nama yang jelas dan satu pintu validasi.