익명 객체 문서의 구현 코드다.

// 익명 객체: 익명 자식 객체, 익명 구현 객체
// 실행: docker run --rm -v "$PWD":/w -w /w eclipse-temurin:21-jdk java 31_java-anonymous-object_impl.java
// 문서(31.익명 객체.md)에 실은 출력과 카드의 답을 직접 비교한다.
import java.io.ByteArrayOutputStream;
import java.io.PrintStream;

class AnonymousCheck {
    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(() -> {
            Button btnOk = new Button();
            btnOk.setClickListener(new Button.ClickListener() {
                @Override
                public void onClick() {
                    System.out.println("OK 버튼을 눌렀다");
                }
            });
            btnOk.click();
        }), "OK 버튼을 눌렀다\n");

        // 익명 자식 객체: 필드, 지역 변수, 매개변수 세 자리
        check("익명 자식 객체 세 자리", capture(() -> {
            Car car = new Car();
            car.run1();
            car.run2();
            car.run3(new Tire() {
                @Override
                void roll() { System.out.println("익명 자식 객체 3이 굴러간다"); }
            });
        }), "익명 자식 객체 1이 굴러간다\n익명 자식 객체 2가 굴러간다\n익명 자식 객체 3이 굴러간다\n");

        // 이름 없는 클래스에도 컴파일러가 붙인 이름이 있다
        Tire plain = new Tire();
        Tire anon = new Tire() {
            @Override
            void roll() { System.out.println("anon"); }
        };
        if (anon.getClass() == Tire.class) throw new AssertionError("익명 객체의 클래스는 Tire가 아니다");
        if (anon.getClass().getSuperclass() != Tire.class) throw new AssertionError("부모는 Tire");
        if (!(anon instanceof Tire)) throw new AssertionError("Tire 타입에 들어간다");
        if (!anon.getClass().getName().equals("AnonymousCheck$3")) throw new AssertionError(anon.getClass().getName());
        if (!anon.getClass().isAnonymousClass() || plain.getClass().isAnonymousClass()) throw new AssertionError("isAnonymousClass");
        System.out.println("ok: 익명 객체의 클래스는 Tire의 자식이고 이름은 " + anon.getClass().getName());

        Button.ClickListener l = new Button.ClickListener() {
            @Override
            public void onClick() { }
        };
        if (l.getClass().getInterfaces()[0] != Button.ClickListener.class) throw new AssertionError("ClickListener를 구현");
        if (l.getClass().getSuperclass() != Object.class) throw new AssertionError("부모는 Object");
        System.out.println("ok: 익명 구현 객체는 ClickListener를 구현한다");

        // 익명 객체는 만들어진 곳의 지역 변수 값을 읽을 수 있다
        check("지역 변수 읽기", capture(() -> {
            String who = "철수";
            Button btn = new Button();
            btn.setClickListener(new Button.ClickListener() {
                @Override
                public void onClick() { System.out.println(who + "가 눌렀다"); }
            });
            btn.click();
        }), "철수가 눌렀다\n");

        // 카드 C3
        check("카드 C3", capture(() -> {
            Tire t1 = new Tire();
            Tire t2 = new Tire() {
                @Override
                void roll() {
                    System.out.println("특수 타이어");
                    super.roll();
                }
            };
            t1.roll();
            t2.roll();
            System.out.println(t1.getClass() == t2.getClass());
        }), "일반 타이어가 굴러간다\n특수 타이어\n일반 타이어가 굴러간다\nfalse\n");

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

class Tire {
    void roll() { System.out.println("일반 타이어가 굴러간다"); }
}

class Car {
    // 필드에 익명 자식 객체를 넣는다
    private Tire tire1 = new Tire() {
        @Override
        void roll() { System.out.println("익명 자식 객체 1이 굴러간다"); }
    };

    void run1() { tire1.roll(); }

    void run2() {
        // 지역 변수에 익명 자식 객체를 넣는다
        Tire tire2 = new Tire() {
            @Override
            void roll() { System.out.println("익명 자식 객체 2가 굴러간다"); }
        };
        tire2.roll();
    }

    // 매개변수로 익명 자식 객체를 받는다
    void run3(Tire tire) { tire.roll(); }
}

class Button {
    static interface ClickListener {
        void onClick();
    }

    private ClickListener clickListener;

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

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