사용자 정의 예외 문서의 구현 코드다.

// 실행: docker run --rm -v "$PWD":/w -w /w eclipse-temurin:21-jdk java 37_java-custom-exception_impl.java
// (JDK 21. 이 파일이 있는 폴더에서 실행한다. 다른 파일을 불러오지 않는다.)
// 문서 「사용자 정의 예외」의 계좌 예제 출력과 카드의 답을 확인한다.
import java.io.ByteArrayOutputStream;
import java.io.PrintStream;

// 맨 위 클래스에 main이 있어야 한 파일 실행이 된다. 예외 클래스와 Account는 아래쪽에 있다.
class CustomExceptionImpl {

    // System.out에 찍히는 글자를 모아 문자열로 돌려준다. 예외가 빠져나오면 그 예외를 그대로 다시 던진다.
    interface Body { void run() throws Throwable; }
    static String capture(Body body) throws Throwable {
        PrintStream old = System.out;
        ByteArrayOutputStream buf = new ByteArrayOutputStream();
        System.setOut(new PrintStream(buf, true));
        try {
            body.run();
        } finally {
            System.setOut(old);
        }
        return buf.toString().replace("\r\n", "\n");
    }
    static void check(boolean ok, String what) {
        if (!ok) throw new AssertionError("FAILED: " + what);
        System.out.println("ok  " + what);
    }
    static void same(String got, String want, String what) {
        if (!got.equals(want)) throw new AssertionError("FAILED: " + what + "\n--- got ---\n" + got + "--- want ---\n" + want);
        System.out.println("ok  " + what);
    }

    // 문서의 계좌 예제. 찾는 돈만 바꿔 가며 돌린다.
    static void example(long money) {
        Account account = new Account();

        account.deposit(10000);
        System.out.println("Deposit: " + account.getBalance());

        try {
            account.withdraw(money);
        } catch (InsufficientException e) {
            String message = e.getMessage();
            System.out.println(message);
        } finally {
            System.out.println("Deposit: " + account.getBalance());
        }
    }

    public static void main(String[] args) throws Throwable {
        same(capture(() -> example(5000)), "Deposit: 10000\nDeposit: 5000\n", "문서 코드 그대로(5000원 찾기): 예외 없음");
        same(capture(() -> example(30000)),
                "Deposit: 10000\nInsufficient balance: need more 20000 won.\nDeposit: 10000\n",
                "30000원 찾기: 예외가 나고 잔액은 그대로");
        same(capture(() -> example(10000)), "Deposit: 10000\nDeposit: 0\n", "경계: 잔액과 같은 돈은 찾을 수 있다");
        same(capture(() -> example(10001)),
                "Deposit: 10000\nInsufficient balance: need more 1 won.\nDeposit: 10000\n", "경계: 1원 모자라면 예외");

        // 종류
        Object e1 = new InsufficientException("m");
        check(e1 instanceof Exception && !(e1 instanceof RuntimeException), "InsufficientException은 일반 예외");
        Object e2 = new NegativeAmountException("m");
        check(e2 instanceof RuntimeException, "NegativeAmountException은 실행 예외");

        // 생성자 두 개
        check(new InsufficientException().getMessage() == null, "기본 생성자로 만들면 메시지는 null");
        check("m".equals(new InsufficientException("m").getMessage()), "super(message)로 넘긴 글이 getMessage()로 나온다");

        // 부모 타입 catch에도 걸린다
        String who = "";
        try { new Account().withdraw(1); } catch (Exception e) { who = e.getClass().getSimpleName(); }
        check(who.equals("InsufficientException"), "catch (Exception e)에도 걸린다");

        // 실행 예외 판: try 없이 불러도 컴파일된다
        String m = "";
        try { new Account().deposit(-5); } catch (NegativeAmountException e) { m = e.getMessage(); }
        check(m.equals("negative amount: -5"), "실행 예외 판의 메시지");

        System.out.println("ALL CHECKS PASSED");
    }
}

// 문서의 InsufficientException (일반 예외)
class InsufficientException extends Exception {
    public InsufficientException() {}
    public InsufficientException(String message) {
        super(message);
    }
}

// 같은 것을 실행 예외로 만든 판 (보충)
class NegativeAmountException extends RuntimeException {
    public NegativeAmountException(String message) {
        super(message);
    }
}

// 문서의 Account
class Account {
    private long balance;

    public Account() {}

    public long getBalance() {
        return balance;
    }

    public void deposit(long money) {
        if (money < 0) {
            throw new NegativeAmountException("negative amount: " + money);   // throws 없이도 컴파일된다
        }
        balance += money;
    }

    public void withdraw(long money) throws InsufficientException {
        if (balance < money) {
            throw new InsufficientException("Insufficient balance: need more " + (money - balance) + " won.");
        }
        balance -= money;
    }
}