Step 4: Transfers are all-or-nothing
A transfer is two changes: take money out of one account, put it into another. If the second step fails after the first succeeded, money has vanished. The code below handles that: it withdraws, then deposits, and if the deposit throws, it rolls back the withdrawal and re-throws. It also adds a fee for large transfers and refuses transfers to the same account. Look at which requests succeed, and check that the total money never changes except for the fee.
import java.util.*;
public class Main {
static class InsufficientFundsException extends RuntimeException {
InsufficientFundsException(String m) { super(m); }
}
static class Account {
final String name;
long cents;
Account(String name, long cents) { this.name = name; this.cents = cents; }
void withdraw(long amount) {
if (amount > cents) throw new InsufficientFundsException(name + " cannot pay " + amount);
cents -= amount;
}
void deposit(long amount) { cents += amount; }
public String toString() { return name + "=" + cents; }
}
// All or nothing: if the deposit side fails, the withdrawal is undone.
static void transfer(Account from, Account to, long amount) {
if (from == to) throw new IllegalArgumentException("cannot transfer to the same account");
long fee = amount >= 10000 ? 100 : 0; // 1.00 fee for large transfers
from.withdraw(amount + fee);
try {
if (to.name.equals("frozen")) throw new IllegalStateException("account frozen");
to.deposit(amount);
} catch (RuntimeException e) {
from.deposit(amount + fee); // roll back
throw e;
}
}
public static void main(String[] args) {
Account ada = new Account("Ada", 50000);
Account sam = new Account("Sam", 1000);
Account frozen = new Account("frozen", 0);
Object[][] requests = { { ada, sam, 2000L }, { sam, ada, 99999L }, { ada, frozen, 500L }, { ada, sam, 10000L }, { ada, ada, 1L } };
for (Object[] r : requests) {
Account from = (Account) r[0], to = (Account) r[1];
long amount = (Long) r[2];
try {
transfer(from, to, amount);
System.out.println("OK " + from.name + " -> " + to.name + " " + amount);
} catch (RuntimeException e) {
System.out.println("ERROR " + from.name + " -> " + to.name + " " + amount + ": " + e.getMessage());
}
}
System.out.println(ada + " " + sam + " " + frozen);
System.out.println("total " + (ada.cents + sam.cents + frozen.cents));
}
}OK Ada -> Sam 2000 ERROR Sam -> Ada 99999: Sam cannot pay 100099 ERROR Ada -> frozen 500: account frozen OK Ada -> Sam 10000 ERROR Ada -> Ada 1: cannot transfer to the same account Ada=37900 Sam=13000 frozen=0 total 50900
Three of the five requests were refused, and the refused ones changed nothing: the frozen account's failed deposit was rolled back, so Ada did not lose 500. The total is 50900, not the starting 51000: the 100 fee on the 10000 transfer was withdrawn from Ada and deposited nowhere. In a real bank the fee would be deposited into a bank-owned account so the books always balance. Adding that account is a good exercise.
What should happen to the first withdrawal if the deposit then fails?
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.