try-catch-finally 문서의 구현 코드다.

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

class TryCatchImpl {

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

    // 실행 순서 표의 코드: A, B, C, D 가운데 무엇이 어떤 순서로 찍히는가
    static int parse(String[] arr, int i) {
        try {
            System.out.println("A");
            int v = Integer.parseInt(arr[i]);
            System.out.println("B");
            return v;
        } catch (NumberFormatException e) {
            System.out.println("C");
            return -1;
        } finally {
            System.out.println("D");
        }
    }

    // 문서의 첫 예제: printLength를 try로 감싼다
    static void printLength(String data) {
        try {
            int result = data.length();
            System.out.println("length: " + result);
        } catch (NullPointerException e) {
            System.out.println(e.getMessage());
        } finally {
            System.out.println("continue..");
        }
    }
    static void withHandling() {
        System.out.println("[Program start]");
        printLength("Hello World");
        printLength(null);
        System.out.println("[Program end]");
    }

    // 문서의 반복문 예제 1: catch 두 개
    static void multi1() {
        String[] array = {"100", "1oo"};
        for (int i = 0; i <= array.length; i++) {
            try {
                int value = Integer.parseInt(array[i]);
                System.out.println("array[" + i + "] = " + value);
            } catch (ArrayIndexOutOfBoundsException e) {
                System.out.println("Array index out of bounds." + e.getMessage());
            } catch (NumberFormatException e) {
                System.out.println("Number format exception." + e.getMessage());
            }
        }
    }

    // 문서의 반복문 예제 2: 자식 catch 먼저, Exception 나중
    static void multi2() {
        String[] array = {"100", "1oo"};
        for (int i = 0; i <= array.length; i++) {
            try {
                int value = Integer.parseInt(array[i]);
                System.out.println("array[" + i + "] = " + value);
            } catch (ArrayIndexOutOfBoundsException e) {
                System.out.println("Array index out of bounds." + e.getMessage());
            } catch (Exception e) {
                System.out.println("There is an exception.");
            }
        }
    }

    // 한 catch에 두 종류를 묶기 (보충)
    static void multi3() {
        String[] array = {"100", "1oo"};
        for (int i = 0; i <= array.length; i++) {
            try {
                int value = Integer.parseInt(array[i]);
                System.out.println("array[" + i + "] = " + value);
            } catch (ArrayIndexOutOfBoundsException | NumberFormatException e) {
                System.out.println("skip: " + e.getClass().getSimpleName());
            }
        }
    }

    // 카드 C2
    static void cardC2() {
        int[] nums = {4, 0};
        for (int i = 0; i < 3; i++) {
            try {
                System.out.println(8 / nums[i]);
            } catch (ArithmeticException e) {
                System.out.println("X");
            } catch (RuntimeException e) {
                System.out.println("Y");
            } finally {
                System.out.println("Z");
            }
        }
        System.out.println("end");
    }

    // 예외가 try 첫 줄에서 나면 try의 나머지 줄은 건너뛴다
    static void skipRest() {
        try {
            System.out.println("1");
            Object o = null;
            o.toString();
            System.out.println("2");
        } catch (NullPointerException e) {
            System.out.println("3");
        }
        System.out.println("4");
    }

    public static void main(String[] args) throws Throwable {
        String[] arr = {"7", "x"};

        // 1. 실행 순서 표
        final int[] r = new int[1];
        same(capture(() -> r[0] = parse(arr, 0)), "A\nB\nD\n", "예외 없음: A B D");
        check(r[0] == 7, "예외 없음: 7을 돌려준다");
        same(capture(() -> r[0] = parse(arr, 1)), "A\nC\nD\n", "잡는 예외: A C D");
        check(r[0] == -1, "잡는 예외: -1을 돌려준다");
        final Throwable[] t = new Throwable[1];
        same(capture(() -> { try { parse(arr, 2); } catch (ArrayIndexOutOfBoundsException e) { t[0] = e; } }),
                "A\nD\n", "못 잡는 예외: A D만 찍고 밖으로 나간다");
        check(t[0] instanceof ArrayIndexOutOfBoundsException, "못 잡는 예외는 부른 쪽으로 간다");

        // 2. 첫 예제
        same(capture(TryCatchImpl::withHandling),
                "[Program start]\nlength: 11\ncontinue..\n"
              + "Cannot invoke \"String.length()\" because \"<parameter1>\" is null\ncontinue..\n[Program end]\n",
                "첫 예제 출력: 끝까지 간다");

        // 3. 반복문 예제 1
        same(capture(TryCatchImpl::multi1),
                "array[0] = 100\n"
              + "Number format exception.For input string: \"1oo\"\n"
              + "Array index out of bounds.Index 2 out of bounds for length 2\n", "반복문 예제 1 출력");

        // 4. 반복문 예제 2
        same(capture(TryCatchImpl::multi2),
                "array[0] = 100\nThere is an exception.\n"
              + "Array index out of bounds.Index 2 out of bounds for length 2\n", "반복문 예제 2 출력");

        // 5. 한 catch에 묶기
        same(capture(TryCatchImpl::multi3),
                "array[0] = 100\nskip: NumberFormatException\nskip: ArrayIndexOutOfBoundsException\n", "catch (A | B) 출력");

        // 6. 카드
        same(capture(TryCatchImpl::cardC2), "2\nZ\nX\nZ\nY\nZ\nend\n", "카드 C2 출력");
        same(capture(TryCatchImpl::skipRest), "1\n3\n4\n", "try의 나머지 줄은 건너뛴다");

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