예외와 예외 클래스 문서의 구현 코드다.

// 실행: docker run --rm -v "$PWD":/w -w /w eclipse-temurin:21-jdk java 34_java-exception_impl.java
// (JDK 21. 이 파일이 있는 폴더에서 실행한다. 다른 파일을 불러오지 않는다.)
// 문서 「예외와 예외 클래스」의 계층 그림과 예제 출력을 확인한다.
import java.io.ByteArrayOutputStream;
import java.io.PrintStream;

class ExceptionImpl {

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

    // 문서의 첫 예제: null을 넘겨도 대비하지 않는다
    static void printLength(String data) {
        int result = data.length();
        System.out.println("length: " + result);
    }
    static void noHandling() {
        System.out.println("[Program start]");
        printLength("Hello World");
        printLength(null);
        System.out.println("[Program end]");
    }

    // 문서의 일반 예외 예제: Class.forName
    static void checkedExample() {
        try {
            Class.forName("java.lang.String");
            System.out.println("There is a String class.");
        } catch (ClassNotFoundException e) {
            System.out.println("There is no String class.");
        }
        System.out.println();
        try {
            Class.forName("java.lang.String2");
            System.out.println("There is a String2 class.");
        } catch (ClassNotFoundException e) {
            System.out.println("There is no String2 class.");
        }
    }

    static void forever(int n) { forever(n + 1); }

    public static void main(String[] args) throws Throwable {
        // 1. 클래스 계층 (문서의 그림)
        check(Exception.class.getSuperclass() == Throwable.class, "Exception의 부모는 Throwable");
        check(Error.class.getSuperclass() == Throwable.class, "Error의 부모는 Throwable");
        check(RuntimeException.class.getSuperclass() == Exception.class, "RuntimeException의 부모는 Exception");
        check(Throwable.class.getSuperclass() == Object.class, "Throwable의 부모는 Object");
        Class<?>[] runtime = { NullPointerException.class, ArrayIndexOutOfBoundsException.class,
                NumberFormatException.class, ArithmeticException.class, ClassCastException.class };
        for (Class<?> c : runtime)
            check(RuntimeException.class.isAssignableFrom(c), c.getSimpleName() + "은 실행 예외");
        Class<?>[] checked = { ClassNotFoundException.class, InterruptedException.class, java.io.IOException.class };
        for (Class<?> c : checked)
            check(Exception.class.isAssignableFrom(c) && !RuntimeException.class.isAssignableFrom(c),
                    c.getSimpleName() + "은 일반 예외");
        Class<?>[] errors = { OutOfMemoryError.class, StackOverflowError.class };
        for (Class<?> c : errors)
            check(Error.class.isAssignableFrom(c) && !Exception.class.isAssignableFrom(c),
                    c.getSimpleName() + "는 Error 쪽이고 Exception이 아니다");

        // 2. 처리하지 않은 실행 예외: 뒤 줄이 실행되지 않는다 (문서의 첫 예제)
        final Throwable[] escaped = new Throwable[1];
        PrintStream old = System.out;
        ByteArrayOutputStream buf = new ByteArrayOutputStream();
        System.setOut(new PrintStream(buf, true));
        try { noHandling(); } catch (NullPointerException e) { escaped[0] = e; } finally { System.setOut(old); }
        same(buf.toString(), "[Program start]\nlength: 11\n", "첫 예제: [Program end]가 찍히지 않는다");
        check(escaped[0] instanceof NullPointerException, "첫 예제: NullPointerException이 main 밖으로 나간다");
        check(escaped[0].getMessage().startsWith("Cannot invoke \"String.length()\" because"),
                "NPE 메시지: " + escaped[0].getMessage());

        // 3. 일반 예외 예제 (Class.forName)
        same(capture(ExceptionImpl::checkedExample),
                "There is a String class.\n\nThere is no String2 class.\n", "일반 예외 예제 출력");

        // 4. Error는 catch (Exception e)에 걸리지 않는다
        boolean caughtAsError = false, caughtAsException = false;
        try { forever(0); } catch (Exception e) { caughtAsException = true; } catch (Error e) { caughtAsError = true; }
        check(caughtAsError && !caughtAsException, "StackOverflowError는 Exception 그물을 빠져나간다");

        // 5. 카드 C3
        Object o = new NumberFormatException("x");
        check(o instanceof IllegalArgumentException && o instanceof RuntimeException && o instanceof Exception
                && o instanceof Throwable && !(o instanceof Error), "NumberFormatException의 조상 줄");

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