SyntaxStudy
Sign Up
Java Encapsulation and Data Hiding
Java Beginner 1 min read

Encapsulation and Data Hiding

Encapsulation is the bundling of data and the methods that operate on that data into a single unit (a class), while restricting direct access to the data from outside the class. In Java this is achieved primarily through access modifiers: private fields are hidden from external code and can only be accessed or modified through public getter and setter methods. This separation of interface from implementation lets you change the internal representation of a class without breaking code that uses it. Access modifiers in Java are private (accessible only within the same class), package-private (the default, accessible within the same package), protected (accessible within the same package and by subclasses), and public (accessible from anywhere). Choosing the most restrictive modifier that still allows the required access is a good design principle. Validation logic belongs in setters. A setter can reject an invalid value and throw an IllegalArgumentException or set a default, ensuring the object is always in a consistent state. Immutable classes go further: they have only private final fields, all set in the constructor, with no setters. Immutability makes objects inherently thread-safe and simplifies reasoning about state.
Example
public class BankAccount {

    // Private fields — no external code can access these directly
    private final String accountNumber;
    private String ownerName;
    private double balance;

    // Constructor validates initial state
    public BankAccount(String accountNumber, String ownerName, double initialBalance) {
        if (accountNumber == null || accountNumber.isBlank())
            throw new IllegalArgumentException("Account number cannot be blank");
        if (initialBalance < 0)
            throw new IllegalArgumentException("Initial balance cannot be negative");
        this.accountNumber = accountNumber;
        this.ownerName     = ownerName;
        this.balance       = initialBalance;
    }

    // Getter — read-only access to final field
    public String getAccountNumber() { return accountNumber; }

    // Getter and validated setter for ownerName
    public String getOwnerName() { return ownerName; }
    public void setOwnerName(String name) {
        if (name == null || name.isBlank())
            throw new IllegalArgumentException("Owner name cannot be blank");
        this.ownerName = name;
    }

    // Getter only — balance is modified only through business methods
    public double getBalance() { return balance; }

    // Business methods enforce invariants
    public void deposit(double amount) {
        if (amount <= 0) throw new IllegalArgumentException("Deposit must be positive");
        balance += amount;
    }

    public void withdraw(double amount) {
        if (amount <= 0) throw new IllegalArgumentException("Withdrawal must be positive");
        if (amount > balance) throw new IllegalStateException("Insufficient funds");
        balance -= amount;
    }

    @Override
    public String toString() {
        return String.format("BankAccount[%s, owner=%s, balance=%.2f]",
            accountNumber, ownerName, balance);
    }

    public static void main(String[] args) {
        BankAccount acc = new BankAccount("ACC-001", "Alice", 500.0);
        acc.deposit(200.0);
        acc.withdraw(100.0);
        System.out.println(acc);
        System.out.println("Balance: " + acc.getBalance());
    }
}