Java 길찾기/이것이 자바다

[Java] 와일드카드 타입(<?>, <? extends ...>, <? super ...>)

Kindbeeeear_ 2022. 4. 6. 19:05

코드에서 ?를 일반적으로 와일드카드(wildcard)라고 부른다. 제네릭 타입을 매개값이나 리턴 타입으로 사용할 때 구체적인 타입 대신에 와일드카드를 다음과 같으 세 가지 형태로 사용할 수 있다.

  • 제네릭타입<?> : Unbounded Wildcards(제한 없음)
    타입 파라미터를 대치하는 구체적인 타입으로 모든 클래스나 인터페이스 타입이 올 수 있다.
  • 제네릭타입<? extends 상위타입> : Upper Bounded Wildcards(상위 클래스 제한)
    타입 파라미터를 대치하는 구체적인 타입으로 상위 타입이나 하위 타입만 올 수 있다.
  • 제네릭타입<? super 하위타입> : Lower Bounded Wildcards(하위 클래스 제한)
    타입 파라미터를 대치하는 구체적인 타입으로 하위 타입이나 상위 타입이 올 수 있다.

설명만으로 잘 이해가 되지 않을 것 같다. 다음 코드를 보면서 이해해보자. 제네릭 타입 Course는 과정 클래스로 과정 이름과 수강생을 저장할 수 있는 배열을 가지고 있다. 타입 파라미터 T가 적용된 곳은 수강생 타입 부분이다.

// Course.java -- 제네릭 타입
public class Course<T> {
    private String name;
    private T[] students;
    
    public Course(String name, int capacity) {
        this.name = name;
        students = (T[]) (new Object[capacity]); // 타입 파라미터로 배열을 생성하려면 new T[n] 형태로 배열을 생성할 수 없다.
    }                                            // (T[]) (new Object[n])으로 생성해야 한다.
    
    public String getName() { return name; }
    public T[] getStudents() { return students; }
    public void add(T t) {  // 배열에 비어있는 부분을 찾아서 수강생을 추가하는 메소드
        for(int i=0; i<students.length; i++) {
            if(students[i] == null) {
                students[i] = t;
                break;
            }
        }
    }
}

 

수강생이 될 수 있는 타입은 다음 4가지 클래스라고 가정하자. Person의 하위 클래스로 Worker와 Student가 있고, Student 하위 클래스로 HighStudent가 있다.

  • Course<?>
    수강생은 모든 타입(Person, Worker, Student, HighStudent)이 될 수 있다.
  • Course<? extends Student>
    수강생은 Student와 HighStudent만 될 수 있다.
  • Course<? super Worker>
    수강생은 Worker와 Person만 될 수 있다.

다음 예제는 registerCouseXXX() 메소드의 매개값으로 와일드카드 타입을 사용하였다. registerCourse()는 모든 수강생이 들을 수 있는 과정을 등록하고, registerCourseStudent()는 학생만 들을 수 있는 과정을 등록한다. 그리고 registerCourseWorker는 직장인만 들을 수 있는 과정을 등록한다.

// WildCardExample.java -- 와일드 카드 타입 매개 변수
import java.util.Arrays;

public class WildCardExample {
    public static void registerCourse( Course<?> couse ) {
        System.out.println(course.getName() + "수강생: " + Arrays.toString(course.getStudents()));
    }
    
    public static void registerCourseStudent( Course<? extends Student> course) {
        System.out.println(course.getName() + "수강생: " + Arrays.toString(course.getStudents()));
    }
    
    public static void registerCourseWorker( Course<? super Worker> course) {
        System.out.println(course.getName() + "수강생: " + Arrays.toString(course.getStudents()));
    }
    
    public static void main(String[] args) {
        Course<Person> personCourse = new Course<Person>("일반인과정", 5);
            personCourse.add(new Person("일반인"));
            personCourse.add(new Worker("직장인"));
            personCourse.add(new Student("학생"));
            personCourse.add(new HighStudent("고등학생"));
        Course<Worker> workerCourse = new Course<Worker>("직장인과정", 5);
            workerCourse.add(new Worker("직장인"));
        Course<Student> studentCourse = new Course<Student>("학생과정", 5);
            studentCourse.add(new Student("학생"));
            studentCourse.add(new HighStudent("고등학생"));
        Course<HighStudent> highStudentCourse = new Course<HighStudent>("고등학생과정", 5);
            highStudentCourse.add(new HighStudent("고등학생"));
        
        // 모든 과정 등록 가능
        registerCourse(personCourse);
        registerCourse(workerCourse);
        registerCourse(studentCourse);
        registerCourse(highStudentCourse);
        System.out.println(); 
        
        // 학생 과정만 등록 가능
        // registerCourseStudent(PersonCourse); // (x)
        // registerCourseStudent(workerCourse); // (x)
        registerCourseStudent(studentCourse);
        resisterCourseStudent(highStudentCourse);
        System.out.println();
        
        // 직장인과 일반인 과정만 등록 가능
        registerCourseWorker(personCourse);
        registerCourseWorker(workerCourse);
        // registerCourseWorker(studentCourse);      // (x) 
        // registerCourseWorker(highStudentCourse);  // (x)
    }
}