제한된 타입 파라미터와 와일드카드 문서의 구현 코드다.

// 실행: docker run --rm -v "$PWD":/w -w /w eclipse-temurin:21-jdk java 39_java-bounded-wildcard_impl.java
// (JDK 21. 이 파일이 있는 폴더에서 실행한다. 다른 파일을 불러오지 않는다.)
// 문서 「제한된 타입 파라미터와 와일드카드」의 예제 출력과 카드의 답을 확인한다.
// 컴파일 오류가 나는 줄은 주석으로 두었다. 오류 문구는 문서에 적은 대로 JDK 21 javac로 따로 확인했다.
import java.io.ByteArrayOutputStream;
import java.io.PrintStream;

class BoundedWildcardImpl {

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

    // 문서의 compare
    public static <T extends Number> boolean compare(T t1, T t2) {
        double v1 = t1.doubleValue();   // Number의 doubleValue()
        double v2 = t2.doubleValue();
        return (v1 == v2);
    }

    // 위쪽 끝을 인터페이스로: 가장 큰 것 고르기 (보충)
    public static <T extends Comparable<T>> T max(T a, T b) {
        return a.compareTo(b) >= 0 ? a : b;
    }

    // 문서의 클래스 관계: Worker와 Student는 Person의 자식
    static class Person {}
    static class Worker extends Person {}
    static class Student extends Person {}
    static class HighStudent extends Student {}
    static class MiddleStudent extends Student {}

    static class Applicant<T> {
        public T kind;
        public Applicant(T kind) {
            this.kind = kind;
        }
    }

    // 문서의 Course
    static class Course {
        // 누구나
        public static void registerCourse1(Applicant<?> applicant) {
            System.out.println(applicant.kind.getClass().getSimpleName() + " registered the Course1.");
        }
        // Student와 그 자식만
        public static void registerCourse2(Applicant<? extends Student> applicant) {
            System.out.println(applicant.kind.getClass().getSimpleName() + " registered the Course2.");
        }
        // Worker와 그 조상만
        public static void registerCourse3(Applicant<? super Worker> applicant) {
            System.out.println(applicant.kind.getClass().getSimpleName() + " registered the Course3.");
        }
    }

    // 세 메서드에 지원자를 넘겨 본다
    static void registerExample() {
        Course.registerCourse1(new Applicant<Person>(new Person()));
        Course.registerCourse1(new Applicant<Worker>(new Worker()));
        Course.registerCourse1(new Applicant<Student>(new Student()));
        Course.registerCourse1(new Applicant<HighStudent>(new HighStudent()));
        Course.registerCourse1(new Applicant<MiddleStudent>(new MiddleStudent()));

        // Course.registerCourse2(new Applicant<Person>(new Person()));   // 컴파일 오류
        // Course.registerCourse2(new Applicant<Worker>(new Worker()));   // 컴파일 오류
        Course.registerCourse2(new Applicant<Student>(new Student()));
        Course.registerCourse2(new Applicant<HighStudent>(new HighStudent()));

        Course.registerCourse3(new Applicant<Person>(new Person()));
        Course.registerCourse3(new Applicant<Worker>(new Worker()));
        // Course.registerCourse3(new Applicant<Student>(new Student()));         // 컴파일 오류
        // Course.registerCourse3(new Applicant<HighStudent>(new HighStudent())); // 컴파일 오류
    }

    public static void main(String[] args) throws Throwable {
        // 1. compare
        check(!compare(10, 20), "compare(10, 20) = false");
        check(compare(10, 10.0), "compare(10, 10.0) = true (둘 다 double 10.0으로 바꿔 견준다)");
        check(compare(4.5, 4.5), "compare(4.5, 4.5) = true");
        check(max(3, 8) == 8 && max("apple", "banana").equals("banana"), "max: Comparable을 구현한 타입만");

        // 2. 세 메서드에 넘긴 출력
        same(capture(BoundedWildcardImpl::registerExample),
                "Person registered the Course1.\nWorker registered the Course1.\nStudent registered the Course1.\n"
              + "HighStudent registered the Course1.\nMiddleStudent registered the Course1.\n"
              + "Student registered the Course2.\nHighStudent registered the Course2.\n"
              + "Person registered the Course3.\nWorker registered the Course3.\n", "세 메서드 출력 9줄");

        // 3. 와일드카드 변수로 받기
        Applicant<? extends Student> a = new Applicant<HighStudent>(new HighStudent());
        Student s = a.kind;                     // 꺼내면 적어도 Student다
        // a.kind = new Student();              // 컴파일 오류: 실제로는 Applicant<HighStudent>일 수 있다
        check(s instanceof HighStudent, "? extends Student: Student로 꺼낸다");

        Applicant<? super Student> b = new Applicant<Person>(new Person());
        b.kind = new Student();                 // Student는 넣을 수 있다
        Object got = b.kind;                    // 꺼내면 Object로만 받는다
        // Student t = b.kind;                  // 컴파일 오류
        check(got instanceof Student, "? super Student: Student를 넣는다");

        Applicant<?> c = a;
        Object any = c.kind;
        check(any != null, "?: Object로만 꺼낸다");

        // 4. 클래스끼리는 부모·자식이어도 Applicant끼리는 아니다
        // Applicant<Person> p = new Applicant<Student>(new Student());   // 컴파일 오류
        Applicant<? extends Person> p = new Applicant<Student>(new Student());   // 이것은 된다
        check(p.kind instanceof Student, "Applicant<? extends Person>으로는 받을 수 있다");

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