제네릭 문서의 구현 코드다.

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

class GenericsImpl {

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

    // 제네릭이 없을 때: 무엇이든 담으려면 Object로 받는다
    static class ObjectBox {
        public Object content;
    }

    // 문서의 Box<T>
    static class Box<T> {
        public T content;
    }

    // 문서의 Product<K, M>
    static class Product<K, M> {
        private K kind;
        private M model;

        public K getKind() { return this.kind; }
        public M getModel() { return this.model; }
        public void setKind(K kind) { this.kind = kind; }
        public void setModel(M model) { this.model = model; }
    }
    static class Tv {}
    static class Car {}

    // 문서의 제네릭 메서드 boxing
    public static <T> Box<T> boxing(T t) {
        Box<T> box = new Box<>();
        box.content = t;
        return box;
    }

    // 문서의 ClassName<E>
    static class ClassName<E> {
        private E element;

        void setElement(E element) {
            this.element = element;
        }

        E get() {
            return element;
        }

        // 메서드가 따로 정한 <E>가 클래스의 E를 가린다
        public <E> E genericMethod1(E o) {
            return o;
        }

        public <T> T genericMethod2(T o) {
            return o;
        }
    }
    static void classNameExample() {
        ClassName<String> a = new ClassName<>();
        ClassName<Integer> b = new ClassName<>();

        a.setElement("10");
        b.setElement(10);

        System.out.println(a.get().getClass().getName());
        System.out.println(b.get().getClass().getName());

        System.out.println(a.genericMethod1(10).getClass().getName());
        System.out.println(b.genericMethod2("10").getClass().getName());
    }

    // 카드 C2
    static <T> T pick(T first, T second) {
        return second;
    }
    static void cardC2() {
        Box<String> b1 = boxing("7");
        Box<Integer> b2 = boxing(7);
        System.out.println(b1.content + b1.content);
        System.out.println(b2.content + b2.content);
        System.out.println(pick("x", "y"));
    }

    @SuppressWarnings({"rawtypes", "unchecked"})
    public static void main(String[] args) throws Throwable {
        // 1. Object 상자: 꺼낼 때마다 강제 변환이 필요하고, 틀리면 실행 중에 터진다
        ObjectBox ob = new ObjectBox();
        ob.content = 100;
        boolean blewUp = false;
        try {
            String s = (String) ob.content;
        } catch (ClassCastException e) {
            blewUp = true;
        }
        check(blewUp, "Object 상자: 잘못 꺼내면 ClassCastException(실행 중)");

        // 2. Box<T>
        Box<String> box1 = new Box<>();
        box1.content = "hello world!";
        String str = box1.content;          // 강제 변환이 없다
        check(str.equals("hello world!"), "Box<String>에서 String을 그대로 꺼낸다");

        // 3. Product<K, M>
        Product<Tv, String> product1 = new Product<>();
        Tv tv1 = new Tv();
        product1.setKind(tv1);
        product1.setModel("Smart Tv");
        Tv tv2 = product1.getKind();
        String tvModel = product1.getModel();
        check(tv2 == tv1 && tvModel.equals("Smart Tv"), "Product<Tv, String>");
        Car car1 = new Car();
        Product<Car, String> product2 = new Product<>();
        product2.setKind(car1);
        product2.setModel("SUV");
        Car car2 = product2.getKind();
        String carModel = product2.getModel();
        check(car2 == car1 && carModel.equals("SUV"), "Product<Car, String>");

        // 4. boxing: 인자를 보고 T를 정한다
        Box<Integer> b1 = boxing(100);
        Box<String> b2 = boxing("안녕하세요");
        check(b1.content == 100 && b2.content.equals("안녕하세요"), "boxing(100), boxing(\"안녕하세요\")");
        check(((Object) b1.content).getClass() == Integer.class, "boxing(100)의 내용물은 Integer(int가 아니다)");

        // 5. ClassName<E>
        same(capture(GenericsImpl::classNameExample),
                "java.lang.String\njava.lang.Integer\njava.lang.Integer\njava.lang.String\n", "ClassName<E> 예제 출력");

        // 6. 타입을 안 적으면 Object로 다룬다
        Box raw = new Box();
        raw.content = "x";
        Object o = raw.content;             // String이 아니라 Object로만 꺼낼 수 있다
        check(o.equals("x"), "타입을 안 적은 Box의 content는 Object");

        // 7. Box<String>과 Box<Integer>는 실행 중에는 같은 클래스 하나다
        check(new Box<String>().getClass() == new Box<Integer>().getClass(), "실행 중 클래스는 Box 하나");

        // 8. 카드
        same(capture(GenericsImpl::cardC2), "77\n14\ny\n", "카드 C2 출력");

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