Step 3: An account that protects its rules
A class is a promise: its methods keep the rules true. Account keeps the balance private, so the only way to change it is deposit or withdraw, and both validate first. Every successful change is appended to a history list, and the getter returns an unmodifiable view so callers cannot rewrite the audit trail. Failures throw exceptions with clear messages and leave the account unchanged.
import java.util.*;
public class Main {
record Money(long cents) {
Money plus(Money o) { return new Money(cents + o.cents); }
Money minus(Money o) { return new Money(cents - o.cents); }
public String toString() { return String.format("$%d.%02d", cents / 100, cents % 100); }
}
record Entry(String kind, Money amount, Money balanceAfter) {}
static class InsufficientFundsException extends RuntimeException {
InsufficientFundsException(String message) { super(message); }
}
static class Account {
private final String owner;
private Money balance = new Money(0);
private final List<Entry> history = new ArrayList<>();
Account(String owner) { this.owner = owner; }
void deposit(Money amount) {
if (amount.cents() <= 0) throw new IllegalArgumentException("deposit must be positive");
balance = balance.plus(amount);
history.add(new Entry("deposit", amount, balance));
}
void withdraw(Money amount) {
if (amount.cents() <= 0) throw new IllegalArgumentException("withdrawal must be positive");
if (amount.cents() > balance.cents()) {
throw new InsufficientFundsException(owner + " has " + balance + " but needs " + amount);
}
balance = balance.minus(amount);
history.add(new Entry("withdraw", amount, balance));
}
Money balance() { return balance; }
List<Entry> history() { return Collections.unmodifiableList(history); } // callers cannot change it
}
public static void main(String[] args) {
Account ada = new Account("Ada");
ada.deposit(new Money(10000));
ada.withdraw(new Money(2550));
String[] attempts = { "withdraw 900.00", "deposit -5.00" };
for (String attempt : attempts) {
try {
if (attempt.startsWith("withdraw")) ada.withdraw(new Money(90000));
else ada.deposit(new Money(-500));
} catch (InsufficientFundsException | IllegalArgumentException e) {
System.out.println("refused: " + e.getMessage());
}
}
for (Entry e : ada.history()) System.out.println(e.kind() + " " + e.amount() + " -> " + e.balanceAfter());
System.out.println("balance " + ada.balance());
}
}refused: Ada has $74.50 but needs $900.00 refused: deposit must be positive deposit $100.00 -> $100.00 withdraw $25.50 -> $74.50 balance $74.50
The two refused requests changed nothing: the balance is still 75.00 after the 25.50 withdrawal. This is the pattern for every operation: check, then change, never change and then check.
Why does history() return an unmodifiable list?
Finish the quick check (and any code exercise) to complete this lesson, or mark it done yourself.
Tip: use the left and right arrow keys to move between lessons.