Hytale Modding
Java Basics

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.

ModifierClassPackageSubclassWorld
public
protected
(tidak ada)
private

Untuk saat ini, fokus pada.

  • public - Bisa diakses oleh siapa saja
  • private - 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 private

Getter 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());  // 0
Penamaan Getter dan Setter

Ikuti 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

Aturan Umum

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 static

Field 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 seterusnya

Method 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 objek

Inisialisasi 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

  1. 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
  2. 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!)
  3. 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
  4. 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