참조 타입과 메모리 영역 문서의 구현 코드다.

// 실행: docker run --rm -v "$PWD":/w -w /w eclipse-temurin:21-jdk java 07_java-reference-types_impl.java
// 참조 타입과 메모리 영역 문서의 출력과 값을 확인한다.

class ReferenceTypesImpl {
    static int maxDepth = 0;

    static void check(boolean ok, String what) {
        if (!ok) throw new AssertionError(what);
    }

    // 문서의 Calculator.add
    static int add(int a, int b) {
        int sum = a + b;
        return sum;
    }

    // 기본 타입 매개변수: 값이 복사되어 들어온다
    static void addTen(int n) {
        n += 10;
    }

    // 참조 타입 매개변수: 주소가 복사되어 들어온다. 같은 배열을 가리킨다
    static void addTenToFirst(int[] arr) {
        arr[0] += 10;
    }

    // 매개변수에 새 배열을 넣어도 부른 쪽 변수는 그대로다
    static void replace(int[] arr) {
        arr = new int[] {7, 7, 7};
        arr[0] = 100;
    }

    static void dive(int depth) {
        maxDepth = depth;
        dive(depth + 1);
    }

    static int factorial(int n) {
        if (n <= 1) return 1;
        return n * factorial(n - 1);
    }

    public static void main(String[] args) {
        // 1. 기본 타입 변수는 값을 담는다. 대입하면 값이 복사된다
        int age = 20;
        int copy = age;
        copy = 30;
        check(age == 20 && copy == 30, "기본 타입은 서로 영향이 없다");

        // 2. 참조 타입 변수는 주소를 담는다. 대입하면 주소가 복사된다
        int[] p = {1, 2, 3};
        int[] q = p;
        q[0] = 99;
        check(p[0] == 99, "q로 고쳤는데 p로 봐도 99");
        check(p == q, "p와 q는 같은 배열을 가리킨다");
        q = new int[] {1, 2, 3};          // q만 새 배열을 가리키게 한다
        q[1] = -5;
        check(p[1] == 2 && p != q, "이제 p와 q는 다른 배열");

        // 3. 메서드를 부르면 스택에 프레임이 생기고, 끝나면 사라진다 (문서의 Calculator)
        int result = add(5, 3);
        check(("5 + 3 = " + result).equals("5 + 3 = 8"), "5 + 3 = 8");
        check(factorial(5) == 120, "재귀: 프레임 다섯 개가 쌓였다 풀린다");

        // 4. 메서드에 넘길 때도 같은 규칙이다
        int n = 5;
        addTen(n);
        check(n == 5, "기본 타입 인자는 바뀌지 않는다");
        int[] data = {5, 6};
        addTenToFirst(data);
        check(data[0] == 15, "배열 인자의 칸은 바뀐다");
        replace(data);
        check(data[0] == 15 && data.length == 2, "매개변수에 새 배열을 넣어도 원래 변수는 그대로");

        // 5. 스택은 끝이 있다. 끝없이 부르면 StackOverflowError
        boolean overflow = false;
        try {
            dive(1);
        } catch (StackOverflowError e) {
            overflow = true;
        }
        check(overflow && maxDepth > 500, "끝없는 재귀는 StackOverflowError (깊이 " + maxDepth + ")");

        // 6. 가리키는 변수가 없어진 객체는 쓰레기가 된다 (수거 시점은 JVM이 정한다)
        int[] big = new int[1000];
        big = null;                       // 이제 그 배열에 닿을 길이 없다
        check(big == null, "변수는 null");

        // 7. String도 참조 타입이다
        String name = "박은지";
        String same = name;
        check(same == name, "같은 String 객체를 가리킨다");

        // 8. 카드
        int[] a = {1, 2};
        int[] b = a;
        int[] c = {1, 2};
        b[1] = 9;
        c[0] = 7;
        check(a[0] == 1 && a[1] == 9, "카드: a = {1, 9}");
        int x = 3;
        int[] arr = {3};
        addTen(x);
        addTenToFirst(arr);
        check(x == 3 && arr[0] == 13, "카드: x = 3, arr[0] = 13");

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