Effective Java - 제네릭(5)

SeungHyuk Shin·2021년 10월 14일
0

Effective Java

목록 보기
18/26
post-thumbnail

[아이템 31]. 한정적 와일드 카드를 사용해 API 유연성을 높이라.


제네릭은 불공변

매개변수화 타입은 불공변(invariant) 이다. 예를 들어 Type1과 Type2가 있을 때, List은 List의 하위 타입 또는 상위 타입이라는 관계가 성립될 수 없다.

조금 더 풀어보면 List에는 어떠한 객체도 넣을 수 있지만 List에는 문자열만 넣을 수 있다. 즉 List이 List의 기능을 제대로 수행하지 못하므로 하위 타입이라고 말할 수 없다.


생산자(Producer)와 와일드카드

우선 Stack 클래스의 public API로 매개변수의 모든 원소를 넣는 메서드를 추가한다고 가정해보자.

컴파일은 정상적으로 수행되지만 아래와 같이 Number 타입으로 선언된 Stack 객체의 메서드에 Integer 타입의 매개변수를 전달하면 컴파일 오류가 발생한다. Integer는 Number의 하위 타입이니 정상적으로 잘 동작할 것만 같지만 incompatible types... Iterable<Integer> cannot be converted to Iterable<Number>와 같은 오류가 발생한다.

앞서 언급한 것처럼 제네릭의 매개변수화 타입은 불공변이기 때문에 상위-하위 자료형의 관계가 없다. 이러한 문제를 해결하려면 한정적 와일드카드(bounded wildcard) 자료형을 사용하면 된다. Integer 클래스는 Number를 상속한 구현체 이므로 아래와 같이 매개변수 부분에 선언한다.

위의 선언을 해석하면 매개변수는 E의 Iterable이 아니라 E의 하위 타입의 Iterable 이라는 뜻이다. Number 클래스를 상속하는 Integer, Long, Double 등의 타입 요소를 가질 수 있게 된다.

직접 정의한 Stack 클래스는 push(E) 메서드를 통해서만 요소를 추가할 수 있다. 따라서 타입 안전성은 확인되지만 elements 배열은 런타임 시에 E[]가 아닌 Object[]가 된다. 역시나 이부분도 런타임 시에 제네릭 타입이 소거되기 때문이다.


소비자(Consumer)와 와일드카드

그럼 이번에는 Stack 인스턴스의 모든 원소를 매개변수로 받은 컬렉션으로 모두 옮기는 popAll 메서드를 작성해보자.

처음 pushAll 메서드를 정의했을 때와 유사한 오류가 발생한다. Collection의 요소 타입과 Stack의 요소 타입이 일치하면 오류는 발생하지 않으나, 위에서 작성한 예제처럼 타입이 일치하지 않으면 컴파일 에러가 발생한다.

Number 클래스는 최상위 Object 클래스를 상속하지만 역시나 제네릭의 매개변수화 타입은 불공변이기 때문에 상속이란 관계가 무의미하다. 동일하게 와일드카드 타입을 사용하면 해결할 수 있는데, popAll 메서드의 매개변수 타입은 E의 컬렉션이 아니라 E의 상위 타입인 Collection 이라고 선언한다.

모든 타입은 자기 자신의 상위 타입이므로 Collection<? super Number>선언은 Collection을 비롯하여 Collection 타입의 매개변수가 전달되어도 오류가 발생하지 않는다.


PECS

Producer-Extends-Consumer-Super… 이렇게 한 글자씩 떼서 PECS 예제로 살펴본 것처럼 코드의 유연성을 높이려면 적절한 와일드카드 타입을 사용해야 한다. 앞에서 생산자(Producer)와 와일드카드, 소비자(Consumer)와 와일드카드를 살펴본 것처럼 상황에 따라서 어떠한 와일드카드 타입을 써야하는지 기억이 나지 않는다면 PECS를 기억하면 된다

0개의 댓글