Tampilan
Kenapa penting
Di bab sebelumnya, siapa pun bisa menulis order.total = 0; atau order.status = "PAID"; tanpa melewati aturan pembayaran. Di aplikasi nyata, celah seperti itu berujung pada saldo negatif, stok minus, atau pesanan yang dianggap lunas padahal belum dibayar. Encapsulation adalah kebiasaan menyembunyikan state object dan hanya membuka operasi yang menjaga aturannya. Ini juga yang membuat kode tim besar tetap bisa diubah: selama method publiknya sama, isi class boleh berubah tanpa merusak pemakainya.
Konsep inti
Access modifier
Java punya empat tingkat akses untuk class, field, constructor, dan method:
| Modifier | Bisa diakses dari | Pemakaian umum |
|---|---|---|
private | class itu sendiri | hampir semua field |
| (tanpa modifier) | class lain di package yang sama | helper internal sebuah package |
protected | package yang sama + subclass (bab Inheritance) | titik perluasan untuk subclass |
public | di mana saja | API yang memang ingin dipakai luar |
Aturan praktis: mulai dari private, lalu buka seperlunya. Field hampir selalu private; method yang menjadi "pintu masuk" object dibuat public (atau package-private bila hanya dipakai di package yang sama).
Constructor: object lahir dalam keadaan valid
Constructor adalah blok yang dijalankan saat new dipanggil. Namanya sama dengan nama class dan tidak punya tipe kembalian. Di sinilah tempat terbaik untuk memvalidasi input, sehingga tidak ada object yang lahir dalam keadaan rusak.
java
class Product {
private String sku;
private String name;
private long price;
private int stock;
Product(String sku, String name, long price) {
this(sku, name, price, 0); // panggil constructor lain di class ini
}
Product(String sku, String name, long price, int stock) {
if (sku == null || sku.isBlank()) {
throw new IllegalArgumentException("SKU wajib diisi");
}
if (price < 0 || stock < 0) {
throw new IllegalArgumentException("Harga dan stok tidak boleh negatif");
}
this.sku = sku;
this.name = name;
this.price = price;
this.stock = stock;
}
String getSku() {
return sku;
}
String getName() {
return name;
}
long getPrice() {
return price;
}
int getStock() {
return stock;
}
void changePrice(long newPrice) {
if (newPrice < 0) {
throw new IllegalArgumentException("Harga tidak boleh negatif");
}
this.price = newPrice;
}
void restock(int quantity) {
if (quantity <= 0) {
throw new IllegalArgumentException("Jumlah restock harus positif");
}
stock += quantity;
}
boolean reserve(int quantity) {
if (quantity <= 0 || quantity > stock) {
return false;
}
stock -= quantity;
return true;
}
}
public class InventoryDemo {
public static void main(String[] args) {
Product coffee = new Product("KOPI-01", "Kopi Gayo", 85_000, 10);
Product tea = new Product("TEH-01", "Teh Melati", 40_000);
System.out.println(coffee.reserve(3) + ", sisa " + coffee.getStock());
System.out.println(coffee.reserve(50) + ", sisa " + coffee.getStock());
tea.restock(20);
System.out.println(tea.getName() + " stok " + tea.getStock());
Product invalid = new Product(" ", "Tanpa SKU", 1_000);
System.out.println("Baris ini tidak pernah dicetak");
}
}Output:
text
true, sisa 7
false, sisa 7
Teh Melati stok 20
Exception in thread "main" java.lang.IllegalArgumentException: SKU wajib diisi
at Product.<init>(InventoryDemo.java:13)
at Product.<init>(InventoryDemo.java:8)
at InventoryDemo.main(InventoryDemo.java:74)Baris at ... adalah stack trace: jejak pemanggilan dari main sampai titik error, dibaca dari bawah ke atas.
Yang terjadi di sini:
throw new IllegalArgumentException("...")menghentikan pembuatan object dan melaporkan alasannya. Untuk sekarang, anggap ini cara standar "menolak input tidak valid"; mekanisme exception — termasuk cara menangkapnya — dibahas di M03.- Constructor overloading: ada dua constructor dengan daftar parameter berbeda, sama seperti overloading method.
this(...)memanggil constructor lain di class yang sama, sehingga validasi cukup ditulis sekali.- Kalau kamu tidak menulis constructor sama sekali, compiler menambahkan constructor default tanpa parameter (itulah yang dipakai
new Product()di bab sebelumnya). Begitu kamu menulis satu constructor, constructor default itu tidak dibuat lagi.
Kode di luar class kini tidak bisa menyentuh field secara langsung. Menambahkan coffee.stock = -5; di main ditolak compiler dengan pesan error: stock has private access in Product.
Java 25: statement sebelum this(...)
Sejak Java 25 (JEP 513, Flexible Constructor Bodies), kamu boleh menulis statement sebelum this(...) atau super(...) — misalnya untuk menormalkan parameter — asalkan statement itu belum memakai object yang sedang dibuat:
java
// Potongan: constructor tambahan di class Product
Product(String rawSku) {
String normalized = rawSku.strip().toUpperCase(); // boleh sejak Java 25
this(normalized, "Tanpa nama", 0);
}Di versi Java sebelumnya, this(...) wajib menjadi statement pertama. Kegunaan utamanya dibahas di bab Inheritance.
this: field milik object ini
Di dalam method instance dan constructor, this adalah referensi ke object yang sedang bekerja. Kamu wajib memakainya saat nama parameter sama dengan nama field — kalau tidak, parameter "menutupi" (shadowing) field:
java
class Customer {
private String name;
void setName(String name) {
name = name; // BUG: parameter diisi dengan dirinya sendiri
}
void rename(String name) {
this.name = name; // benar: field milik object ini
}
String getName() {
return name;
}
}
public class ShadowDemo {
public static void main(String[] args) {
Customer customer = new Customer();
customer.setName("Budi");
System.out.println(customer.getName());
customer.rename("Budi");
System.out.println(customer.getName());
}
}Output: null, lalu Budi. Bug pertama lolos kompilasi tanpa peringatan — salah satu alasan banyak tim memakai this. secara konsisten di constructor dan setter.
Getter, setter, dan method bermakna
Getter (getName(), untuk boolean biasanya isActive()) memberi akses baca. Setter (setName(...)) memberi akses tulis. Konvensi nama ini dikenal sebagai gaya JavaBeans dan dipakai banyak framework — misalnya library JSON dan JPA membaca/mengisi object lewat getter dan setter.
Namun tidak setiap field butuh setter. Tanyakan: apakah perubahan ini punya aturan bisnis?
skudanidbiasanya tidak boleh berubah setelah object dibuat → cukup getter.stocktidak diubah lewatsetStock(int), melainkan lewat operasi yang bermakna:restock,reserve. Setiap operasi memeriksa aturannya sendiri.priceboleh berubah, tapi lewatchangePriceyang menolak harga negatif.
Method bermakna membuat kode pemakai terbaca seperti proses bisnisnya (order.pay(...), product.reserve(...)), dan aturan tidak bisa dilewati. Setter "bodoh" yang menerima nilai apa saja pada dasarnya sama dengan field public.
Package: pengelompokan dan batas akses
Di project nyata, class dikelompokkan dalam package — semacam folder bernama yang juga menjadi batas akses. Nama package memakai domain terbalik, dan foldernya mengikuti nama itu:
java
// Potongan: file src/main/java/id/toko/inventory/Product.java
package id.toko.inventory;
public class Product {
// ...
}java
// Potongan: file src/main/java/id/toko/order/OrderService.java
package id.toko.order;
import id.toko.inventory.Product; // class dari package lain perlu di-import
public class OrderService {
// hanya anggota `public` milik Product yang terlihat dari sini
}Latihan di exercises/ memakai package id.jalurjava.m01, id.jalurjava.m02, dan seterusnya. Contoh di bab ini ditulis tanpa package agar bisa langsung dijalankan dengan java File.java.
Contoh di dunia backend
Mari tutup celah class Order dari bab sebelumnya. Semua field menjadi private, ID wajib diisi saat dibuat, dan status hanya berubah lewat pay atau cancel:
java
class Order {
private String id;
private long total;
private int itemCount;
private String status;
Order(String id) {
if (id == null || id.isBlank()) {
throw new IllegalArgumentException("ID order wajib diisi");
}
this.id = id;
this.status = "NEW";
}
boolean addItem(long unitPrice, int quantity) {
if (!status.equals("NEW") || unitPrice < 0 || quantity <= 0) {
return false;
}
total += unitPrice * quantity;
itemCount += quantity;
return true;
}
boolean pay(long amount) {
if (!status.equals("NEW") || itemCount == 0 || amount != total) {
return false;
}
status = "PAID";
return true;
}
boolean cancel() {
if (!status.equals("NEW")) {
return false;
}
status = "CANCELLED";
return true;
}
String getId() {
return id;
}
long getTotal() {
return total;
}
String getStatus() {
return status;
}
}
public class OrderDemo {
public static void main(String[] args) {
Order order = new Order("INV-001");
order.addItem(85_000, 2);
order.addItem(40_000, 1);
System.out.println(order.getId() + " total Rp" + order.getTotal());
System.out.println(order.pay(210_000) + " → " + order.getStatus());
System.out.println(order.addItem(10_000, 1) + " → total tetap Rp" + order.getTotal());
System.out.println(order.cancel() + " → " + order.getStatus());
}
}Output:
text
INV-001 total Rp210000
true → PAID
false → total tetap Rp210000
false → PAIDSetelah dibayar, order tidak bisa ditambah item atau dibatalkan, dan tidak ada kode lain yang bisa memaksa status berubah. Di aplikasi Spring Boot, aturan seperti ini biasanya tinggal di class domain atau service; controller hanya memanggil order.pay(...) tanpa perlu tahu detailnya. Status masih berupa String yang rawan salah ketik — bab Record & Enum akan memperbaikinya.
Pertanyaan interview
1. Apa itu encapsulation dan kenapa penting?
Encapsulation berarti menyembunyikan state internal object (field private) dan hanya membuka operasi yang menjaga aturannya. Manfaatnya: object selalu dalam keadaan valid, aturan bisnis ada di satu tempat, dan isi class bisa diubah tanpa merusak kode pemakainya selama API publiknya sama.
2. Apakah setiap field harus punya getter dan setter?
Tidak. Setter yang menerima nilai apa saja melemahkan encapsulation. Field yang tidak boleh berubah (ID, SKU) cukup punya getter, dan perubahan yang punya aturan sebaiknya lewat method bermakna seperti deposit, reserve, atau pay yang memvalidasi input.
3. Jelaskan beda private, package-private, protected, dan public.
private: hanya class itu. Package-private (tanpa modifier): semua class di package yang sama. protected: package yang sama ditambah subclass. public: semua class. Pilih akses paling sempit yang masih memenuhi kebutuhan.
Jebakan umum
- Lupa
this.saat nama parameter sama dengan field → field tidak pernah terisi, tanpa error kompilasi. - Generate getter/setter untuk semua field secara otomatis dari IDE. Pikirkan dulu field mana yang memang boleh diubah, dan lewat aturan apa.
- Validasi di tempat yang salah. Validasi di controller saja tidak cukup bila object bisa dibuat dari tempat lain; constructor dan method object adalah penjaga terakhir.
- Menambah constructor lalu kaget
new Product()gagal. Constructor default hilang begitu ada constructor lain. - Mengembalikan array internal lewat getter. Pemanggil bisa mengubah isinya; kembalikan salinan bila perlu.
Ringkasan
- Encapsulation: field
private, perubahan hanya lewat method yang menjaga aturan. - Access modifier:
private→ package-private →protected→public; mulai dari yang paling sempit. - Constructor memastikan object lahir valid; boleh di-overload dan saling memanggil dengan
this(...). thismerujuk object yang sedang bekerja; wajib saat parameter menutupi field.- Getter untuk membaca; setter hanya bila perubahan bebas memang masuk akal — selebihnya pakai method bermakna.
- Package mengelompokkan class dan menjadi batas akses; class dari package lain perlu di-
import.