중첩 클래스 문서의 구현 코드다.

// 중첩 클래스: 인스턴스 멤버 클래스, 정적 멤버 클래스, 로컬 클래스, 바깥 멤버 접근, 중첩 인터페이스
// 실행: docker run --rm -v "$PWD":/w -w /w eclipse-temurin:21-jdk java 30_java-nested-class_impl.java
// 문서(30.중첩 클래스.md)에 실은 출력과 카드의 답을 직접 비교한다.
import java.io.ByteArrayOutputStream;
import java.io.PrintStream;

class NestedClassCheck {
    static String capture(Runnable r) {
        PrintStream old = System.out;
        ByteArrayOutputStream buf = new ByteArrayOutputStream();
        System.setOut(new PrintStream(buf));
        try { r.run(); } finally { System.setOut(old); }
        return buf.toString();
    }

    static void check(String name, String got, String want) {
        if (!got.equals(want)) throw new AssertionError(name + "\n--- got ---\n" + got + "--- want ---\n" + want);
        System.out.println("ok: " + name);
    }

    public static void main(String[] args) {
        // 세 종류를 만드는 법
        check("세 종류 만들기", capture(() -> {
            A a = new A();
            A.B b = a.new B();      // 인스턴스 멤버 클래스: A 객체가 먼저 있어야 한다
            b.show();
            A.C c = new A.C();      // 정적 멤버 클래스: A 객체 없이 만든다
            c.show();
            a.method();             // 로컬 클래스: 메서드가 실행되는 동안에만 만든다
        }), "B: 10, 20\nC: 20\nD: 10, 30\n");

        // 인스턴스 멤버 클래스의 객체는 자기를 만든 바깥 객체에 묶인다
        check("바깥 객체에 묶인다", capture(() -> {
            A a1 = new A();
            A a2 = new A();
            a2.field = 99;
            A.B b1 = a1.new B();
            A.B b2 = a2.new B();
            b1.show();
            b2.show();
        }), "B: 10, 20\nB: 99, 20\n");

        // 바깥클래스이름.this
        check("Outer.this", capture(() -> {
            Outer outer = new Outer();
            Outer.Inner inner = outer.new Inner();
            inner.print();
        }), "local\ninner\nouter\n");

        // 컴파일하면 클래스 파일 이름은 바깥$안
        if (!A.B.class.getName().equals("A$B")) throw new AssertionError(A.B.class.getName());
        if (!A.C.class.getName().equals("A$C")) throw new AssertionError(A.C.class.getName());
        System.out.println("ok: 클래스 이름 A$B, A$C");

        // 중첩 인터페이스: Button.ClickListener
        check("중첩 인터페이스", capture(() -> {
            Button btnOk = new Button();
            btnOk.setClickListener(new OkListener());
            btnOk.click();
            btnOk.setClickListener(new CancelListener());
            btnOk.click();
        }), "OK 버튼을 눌렀다\nCancel 버튼을 눌렀다\n");

        // 카드 C3
        check("카드 C3", capture(() -> {
            Outer o1 = new Outer();
            Outer o2 = new Outer();
            o2.name = "second";
            Outer.Inner in = o2.new Inner();
            o1.name = "first";
            in.print();
        }), "local\ninner\nsecond\n");

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

class A {
    int field = 10;            // 인스턴스 필드
    static int sfield = 20;    // 정적 필드

    class B {                  // 인스턴스 멤버 클래스
        void show() { System.out.println("B: " + field + ", " + sfield); }
    }

    static class C {           // 정적 멤버 클래스
        void show() { System.out.println("C: " + sfield); }   // field는 쓸 수 없다
    }

    void method() {
        int local = 30;
        class D {              // 로컬 클래스
            void show() { System.out.println("D: " + field + ", " + local); }
        }
        D d = new D();
        d.show();
    }
}

class Outer {
    String name = "outer";

    class Inner {
        String name = "inner";

        void print() {
            String name = "local";
            System.out.println(name);             // 지역 변수
            System.out.println(this.name);        // Inner 객체의 필드
            System.out.println(Outer.this.name);  // 바깥 Outer 객체의 필드
        }
    }
}

class Button {
    static interface ClickListener {   // 정적 중첩 인터페이스
        void onClick();
    }

    private ClickListener clickListener;

    void setClickListener(ClickListener clickListener) {
        this.clickListener = clickListener;
    }

    void click() {
        this.clickListener.onClick();
    }
}

class OkListener implements Button.ClickListener {
    @Override
    public void onClick() { System.out.println("OK 버튼을 눌렀다"); }
}

class CancelListener implements Button.ClickListener {
    @Override
    public void onClick() { System.out.println("Cancel 버튼을 눌렀다"); }
}