08 - Enkapsulasi dan Akses Modifier
Pelajari cara melindungi dan mengontrol akses ke data class-mu.
Enkapsulasi adalah konsep untuk menyembunyikan detail internal dari sebuah class dan mengontrol bagaimana datanya diakses dan dimodifikasi. Ini bertujuan untuk mencegah bug dan membuat kodemu lebih mudah dipelihara.
Masalah Tanpa Enkapsulasi
public class Player {
public String name;
public int health;
public int maxHealth;
}
public class Main {
public static void main(String[] args) {
Player player = new Player();
player.health = 100;
player.maxHealth = 100;
// Ups! Siapa saja bisa melanggar aturan
player.health = 500; // Health melebihi batas maksimal!
player.health = -50; // Health negatif!
player.name = ""; // Nama kosong!
}
}Tanpa perlindungan, siapa saja bisa mengatur nilai yang tidak valid!
Akses Modifier
Java memiliki kata kunci untuk mengontrol siapa saja yang bisa mengakses member dari class-mu.
| Modifier | Class | Package | Subclass | World |
|---|---|---|---|---|
public | ✓ | ✓ | ✓ | ✓ |
protected | ✓ | ✓ | ✓ | ✗ |
| (tidak ada) | ✓ | ✓ | ✗ | ✗ |
private | ✓ | ✗ | ✗ | ✗ |
Untuk saat ini, fokus pada.
public- Bisa diakses oleh siapa sajaprivate- Hanya bisa diakses di dalam class itu sendiri
Membuat Properti Private
public class Player {
private String name;
private int health;
private int maxHealth;
public Player(String name, int maxHealth) {
this.name = name;
this.health = maxHealth;
this.maxHealth = maxHealth;
}
}Sekarang kamu tidak bisa mengakses properti secara langsung:
Player player = new Player("Alice", 100);
player.health = 500; // ❌ Error! health bersifat privateGetter dan Setter
Untuk mengakses properti private, buat method getter dan setter*:
public class Player {
private String name;
private int health;
private int maxHealth;
public Player(String name, int maxHealth) {
this.name = name;
this.health = maxHealth;
this.maxHealth = maxHealth;
}
// Getter - mengambil nilai
public int getHealth() {
return health;
}
// Setter - mengatur nilai dengan validasi
public void setHealth(int health) {
if (health < 0) {
this.health = 0;
} else if (health > maxHealth) {
this.health = maxHealth;
} else {
this.health = health;
}
}
public String getName() {
return name;
}
public int getMaxHealth() {
return maxHealth;
}
}Sekarang interaksi dengan objek menjadi lebih aman:
Player player = new Player("Alice", 100);
player.setHealth(150); // Otomatis dibatasi sampai 100
System.out.println(player.getHealth()); // 100
player.setHealth(-20); // Otomatis diatur ke 0
System.out.println(player.getHealth()); // 0Ikuti konvensi penamaan Java:
- Getter:
get+ nama properti (dengan huruf kapital di awal) - Setter:
set+ nama properti (dengan huruf kapital di awal) - Boolean:
is+ nama properti (dengan huruf kapital di awal)
private int health;
public int getHealth() { }
public void setHealth(int health) { }
private boolean alive;
public boolean isAlive() { }
public void setAlive(boolean alive) { }
private String name;
public String getName() { }
public void setName(String name) { }Manfaat Enkapsulasi
1. Validasi
public class Item {
private int durability;
private int maxDurability;
public void setDurability(int durability) {
if (durability < 0) {
this.durability = 0;
} else if (durability > maxDurability) {
this.durability = maxDurability;
} else {
this.durability = durability;
}
}
public boolean isBroken() {
return durability <= 0;
}
}2. Properti Read-Only (Hanya Baca)
Terkadang kamu tidak ingin setter:
public class Monster {
private String id; // Tidak boleh berubah
private int health;
public Monster(String id, int health) {
this.id = id;
this.health = health;
}
// Hanya getter - tidak ada setter!
public String getId() {
return id;
}
public int getHealth() {
return health;
}
public void setHealth(int health) {
this.health = health;
}
}3. Properti yang Dihitung (Computed Properties)
Getter tidak harus mengembalikan field secara langsung:
public class Player {
private int health;
private int maxHealth;
public int getHealth() {
return health;
}
// Computed yang dihitung
public double getHealthPercentage() {
return (health * 100.0) / maxHealth;
}
// Computed yang dihitung
public boolean isLowHealth() {
return getHealthPercentage() < 25;
}
}Contoh Praktis
Item dengan Durabilitas
public class Tool {
private String name;
private int durability;
private int maxDurability;
private boolean broken;
public Tool(String name, int maxDurability) {
this.name = name;
this.durability = maxDurability;
this.maxDurability = maxDurability;
this.broken = false;
}
public void use() {
if (broken) {
System.out.println(name + " is broken!");
return;
}
durability--;
System.out.println(name + " used. Durability: " + durability);
if (durability <= 0) {
broken = true;
System.out.println(name + " broke!");
}
}
public void repair() {
durability = maxDurability;
broken = false;
System.out.println(name + " repaired!");
}
// Getters
public String getName() {
return name;
}
public int getDurability() {
return durability;
}
public boolean isBroken() {
return broken;
}
public double getDurabilityPercentage() {
return (durability * 100.0) / maxDurability;
}
}Contoh Akun Bank
public class PlayerWallet {
private int gold;
private int silver;
public PlayerWallet() {
this.gold = 0;
this.silver = 0;
}
public void addGold(int amount) {
if (amount > 0) {
gold += amount;
System.out.println("Added " + amount + " gold");
}
}
public boolean spendGold(int amount) {
if (amount > gold) {
System.out.println("Not enough gold!");
return false;
}
gold -= amount;
System.out.println("Spent " + amount + " gold");
return true;
}
public int getGold() {
return gold;
}
public int getTotalValue() {
// 1 gold = 100 silver
return gold * 100 + silver;
}
}Sistem Blok Terlindungi
public class ProtectedBlock {
private int x, y, z;
private String type;
private String owner;
private boolean locked;
public ProtectedBlock(int x, int y, int z, String type, String owner) {
this.x = x;
this.y = y;
this.z = z;
this.type = type;
this.owner = owner;
this.locked = true;
}
public boolean canBreak(String playerName) {
if (!locked) {
return true;
}
return playerName.equals(owner);
}
public void unlock(String playerName) {
if (playerName.equals(owner)) {
locked = false;
System.out.println("Block unlocked");
} else {
System.out.println("You don't own this block!");
}
}
// Hanya getters - posisi dan pemilik tidak boleh berubah
public int getX() {
return x;
}
public int getY() {
return y;
}
public int getZ() {
return z;
}
public String getOwner() {
return owner;
}
public boolean isLocked() {
return locked;
}
}Kapan Menggunakan Private vs Public
Jadikan private secara default! Hanya jadikan public jika memang perlu diakses dari luar.
Private:
- Data internal (health, posisi, inventory)
- Method pembantu yang hanya digunakan dalam class
- Apapun yang membutuhkan validasi
Public:
- Method yang mendefinisikan perilaku class
- Konstruktor
- Method yang perlu dipanggil oleh class lainnya
public class Example {
// Private - data internal
private int internalCounter;
private String secretKey;
// Public - bagian dari interface
public void doSomething() {
// Menggunakan method pembantu private
validateData();
}
// Private - method pembantu internal
private void validateData() {
// ...
}
}Kata Kunci final
final berarti variabel tidak bisa diubah setelah diatur:
public class Player {
private final String id; // Tidak bisa berubah setelah dibuat
private String name; // Bisa berubah
private int health; // Bisa berubah
public Player(String id, String name) {
this.id = id;
this.name = name;
}
public String getId() {
return id;
}
// Tidak ada setId() - karena final!
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}Kata Kunci static
Member Static
Sebuah class dapat mendefinisikan dua jenis member:
-
Member instance — dimiliki oleh setiap objek (setiap instance memiliki salinannya sendiri).
-
Member static — dimiliki oleh class (satu salinan bersama untuk seluruh tipe).
Sederhananya: member instance dimiliki oleh objek, sedangkan member static dimiliki oleh kelas itu sendiri dan digunakan bersama oleh semua objek dari tipe tersebut.
Deklarasi
/* (akses modifier) */ static ... namaMember; Contoh
class Data {
public int x; // Instanced member
public static int y = 1000; // Static member
// Member instance:
// bisa mengakses member static dan non-static
public void foo() {
x = 100; // OK - sama dengan this.x = 100;
y = 100; // OK - sama dengan Data.y = 200;
}
// Member static:
// tidak bisa mengakses variabel non-static
public static void bar() {
x = 100; // Error: variabel non-static x tidak bisa direferensikan dari konteks static
y = 100; // OK
}
}Mengakses member static
Data data = new Data();
data.x = 1000; // OK
data.y = 1000; // OK-ish - tidak terlalu disarankan; lebih baik menggunakan Data.y
Data.y = 1000; // OK - praktik terbaik
Data.x = 1000; // Error: tidak bisa mengakses variabel instance dalam konteks staticField Static
Field static merepresentasikan member data yang dimiliki oleh tipe class, bukan oleh objek. Field static juga disimpan pada lokasi memori tertentu yang digunakan bersama oleh semua instance objek yang dibuat.
Dideklarasikan sebagai berikut:
/* (akses modifier) (opsional) */ static /* final/volatile (opsional) */ namaField;Mari ambil contoh class Data yang sama dan tambahkan konstruktor ini:
public Data() {
y++; // ingat bahwa ini sama dengan Data.y++;
}// Setiap instance Data akan memiliki salinan private dari member instance x
// Namun, ini akan menunjuk ke lokasi yang sama di memori untuk member y
Data d1 = new Data(); // y = 1001
d1.x = 5;
Data d2 = new Data(); // y = 1002
d2.x = 25;
Data d3 = new Data(); // y = 1003
// ... dan seterusnyaMethod Static
Method static pada dasarnya merepresentasikan member fungsi dari tipe kelas tertentu
Dari kelas Data, ingat kembali method instance foo dan method static bar.
Method-method tersebut dapat diakses melalui:
Data d1 = new Data();
d1.foo(); // Method instance: Hanya dapat diakses oleh objek
Data.bar(); // Method static: dapat diakses tanpa objekInisialisasi Static
Gunakan blok static initializer untuk menjalankan logika inisialisasi saat class pertama kali dimuat:
class OtherData {
private static int a = 12;
private static int b;
private static String msg;
static {
msg = "Inisialisasi..."
System.out.println(msg);
b = 4;
// ... inisialisasi kompleks yang tidak bisa dilakukan dalam satu ekspresi
}
}Latihan Praktik
-
Buat Class
BankAccount:- Properti private: accountNumber, balance
- Konstruktor untuk mengatur nomor akun
- Method: deposit(), withdraw(), getBalance()
- Validasi: tidak bisa menarik dana melebihi saldo
- Nomor akun harus read-only
-
Buat Class
Door:- Properti private: isLocked, keyCode
- Konstruktor untuk mengatur kode kunci
- Method: lock(), unlock(String code), isLocked()
- unlock() hanya berfungsi jika menggunakan kode yang benar
- Kode harus private (jangan diekspos!)
-
Buat kelas
PlayerStats:- Properti private: strength, defense, speed
- Kontruktor untuk mengator semua stats
- Getter untuk semua stats
- Method: getPowerLevel() yang mengembalikan strength + defense + speed
- Stats tidak boleh negatif atau lebih dari 100
-
Refactor sebuah Class: Ambil salah satu class-mu dari pelajaran sebelumnya dan tambahkan enkapsulasi yang sesuai:
- Jadikan semua properti private
- Tambahkan getter dan setter yang sesuai
- Tambahkan validasi jika diperlukan