Skip to content

Encapsulation & Information Hiding


Definition

Encapsulation Definition


Violation Example

// BAD: Exposed internal state
public class BankAccount {
    public double balance;      // Anyone can set!
    public List<Transaction> transactions;  // Direct access!
    public String accountNumber;

    public void deposit(double amount) {
        balance += amount;
        transactions.add(new Transaction("deposit", amount));
    }
}

// Problems:
BankAccount account = new BankAccount();
account.balance = 1000000;        // Fraud!
account.balance = -500;           // Invalid state!
account.transactions.clear();     // Evidence destroyed!
account.transactions = null;      // NPE waiting to happen!

// Also can't change implementation:
// - What if we want to store balance as cents (long)?
// - What if transactions should be stored in DB?
// - All code using these fields would break!

Proper Encapsulation

// GOOD: Properly encapsulated
public class BankAccount {
    private BigDecimal balance;
    private final List<Transaction> transactions;
    private final String accountNumber;

    public BankAccount(String accountNumber) {
        this.accountNumber = accountNumber;
        this.balance = BigDecimal.ZERO;
        this.transactions = new ArrayList<>();
    }

    public void deposit(Money amount) {
        if (amount.isNegative()) {
            throw new IllegalArgumentException("Deposit must be positive");
        }
        this.balance = this.balance.add(amount.getValue());
        this.transactions.add(Transaction.deposit(amount));
    }

    public void withdraw(Money amount) {
        if (amount.isGreaterThan(this.balance)) {
            throw new InsufficientFundsException();
        }
        this.balance = this.balance.subtract(amount.getValue());
        this.transactions.add(Transaction.withdrawal(amount));
    }

    public Money getBalance() {
        return new Money(balance);  // Return copy, not reference
    }

    public List<Transaction> getTransactionHistory() {
        return Collections.unmodifiableList(transactions);  // Read-only!
    }

    // No setter for balance - only through deposit/withdraw
    // No setter for accountNumber - immutable after creation
}

// Now:
// ✓ Can't set invalid balance
// ✓ Can't modify transactions directly
// ✓ Can change internal representation freely
// ✓ All mutations go through validated methods

Defensive Copying

// Protect mutable objects from external modification

public class Event {
    private final String name;
    private final Date startDate;
    private final List<String> attendees;

    // BAD: Direct assignment
    public Event(String name, Date startDate, List<String> attendees) {
        this.name = name;
        this.startDate = startDate;          // Reference to external Date!
        this.attendees = attendees;          // Reference to external List!
    }

    // GOOD: Defensive copying
    public Event(String name, Date startDate, List<String> attendees) {
        this.name = name;
        this.startDate = new Date(startDate.getTime());  // Copy!
        this.attendees = new ArrayList<>(attendees);     // Copy!
    }

    // BAD: Return internal reference
    public Date getStartDate() {
        return startDate;  // Caller can modify!
    }

    // GOOD: Return copy
    public Date getStartDate() {
        return new Date(startDate.getTime());  // Return copy!
    }

    // BETTER: Use immutable types (Java 8+)
    private final LocalDateTime startDateTime;  // Immutable!
    private final List<String> attendees;

    public List<String> getAttendees() {
        return List.copyOf(attendees);  // Immutable copy
    }
}

Access Modifiers

Java Access Modifiers


Tell, Don't Ask

// Information hiding encourages "Tell, Don't Ask"

// BAD: Asking for data, making decision outside
class OrderProcessor {
    void processOrder(Order order) {
        // Asking for internal state
        if (order.getStatus() == Status.PENDING &&
            order.getPaymentStatus() == PaymentStatus.PAID &&
            order.getItems().size() > 0) {
            // Then do something...
        }
    }
}

// GOOD: Tell the object what to do
class Order {
    // Order knows its own rules
    public boolean canBeProcessed() {
        return status == Status.PENDING &&
               paymentStatus == PaymentStatus.PAID &&
               !items.isEmpty();
    }

    public void process() {
        if (!canBeProcessed()) {
            throw new IllegalStateException("Cannot process order");
        }
        // Process...
    }
}

class OrderProcessor {
    void processOrder(Order order) {
        order.process();  // Tell, don't ask!
    }
}

// Benefits:
// - Logic about Order is in Order class
// - Can change Order internals without affecting OrderProcessor
// - Single place for validation logic

Module-Level Encapsulation

Module-Level Encapsulation


Tips & Tricks

Encapsulation Tips