예외 던지기 문서의 구현 코드다.

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

class ThrowImpl {

    // 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);
    }

    // 문서의 첫 예제: findClass가 예외를 부른 쪽으로 넘긴다
    static void findClass() throws ClassNotFoundException {
        Class.forName("java.lang.String2");
    }
    static void exampleMain() {
        try {
            findClass();
        } catch (ClassNotFoundException e) {
            System.out.println("Exception: " + e.getMessage());
        }
    }

    // 떠넘기기가 여러 층을 거치는 흐름 (실행 순서 표)
    static void c() throws Exception {
        System.out.println("c1");
        throw new Exception("boom");
    }
    static void b() throws Exception {
        System.out.println("b1");
        c();
        System.out.println("b2");
    }
    static void a() {
        try {
            System.out.println("a1");
            b();
            System.out.println("a2");
        } catch (Exception e) {
            System.out.println("caught " + e.getMessage());
        }
        System.out.println("a3");
    }

    // throw로 직접 던지기
    static int divide(int x, int y) {
        if (y == 0) {
            throw new IllegalArgumentException("y must not be 0");
        }
        return x / y;
    }

    // 여러 종류를 쉼표로 적기, 그리고 throws Exception 하나로 뭉치기
    static void two(String name, String number) throws ClassNotFoundException, NumberFormatException {
        Class.forName(name);
        Integer.parseInt(number);
    }
    static void all(String name, String number) throws Exception {
        Class.forName(name);
        Integer.parseInt(number);
    }

    // 카드 C2
    static void inner() throws Exception {
        try {
            System.out.println("1");
            throw new Exception("E");
        } finally {
            System.out.println("2");
        }
    }
    static void cardC2() {
        try {
            inner();
            System.out.println("3");
        } catch (Exception e) {
            System.out.println("4 " + e.getMessage());
        }
        System.out.println("5");
    }

    public static void main(String[] args) throws Throwable {
        same(capture(ThrowImpl::exampleMain), "Exception: java.lang.String2\n", "첫 예제 출력");
        same(capture(ThrowImpl::a), "a1\nb1\nc1\ncaught boom\na3\n", "세 층 떠넘기기: b2, a2는 건너뛴다");

        check(divide(6, 3) == 2, "divide(6, 3) = 2");
        String msg = null;
        try { divide(6, 0); } catch (IllegalArgumentException e) { msg = e.getMessage(); }
        check("y must not be 0".equals(msg), "throw에 넣은 메시지를 getMessage()로 꺼낸다");

        String kind = "";
        try { two("java.lang.String2", "1"); } catch (ClassNotFoundException e) { kind = "CNF"; }
        check(kind.equals("CNF"), "two: ClassNotFoundException");
        try { two("java.lang.String", "1oo"); } catch (NumberFormatException e) { kind = "NF"; }
        check(kind.equals("NF"), "two: NumberFormatException");
        try { all("java.lang.String", "1oo"); } catch (Exception e) { kind = e.getClass().getSimpleName(); }
        check(kind.equals("NumberFormatException"), "all: Exception 하나로 받아도 실제 객체의 종류는 그대로");

        same(capture(ThrowImpl::cardC2), "1\n2\n4 E\n5\n", "카드 C2 출력");

        // 생성자 인자 없이 만들면 메시지는 null
        check(new Exception().getMessage() == null, "new Exception().getMessage()는 null");

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