※ 공변과 불공변
=> 공변(covariant) : A가 B의 하위 타입일 때, T<A>가 T<B>의 하위 타입이면 T는 공변
=> 불공변(invariant) : A가 B의 하위 타입일 때, T<A>가 T<B>의 하위 타입이 아니면 T는 불공변
※ 제네릭이 만들어진 이유
=> 제네릭 사용이전 collection 사용(타입이 정의되지 않은)
=> 컬렉션 속의 요소들의 타입이 정의되지 않음
=> 타입이 보장되지 않아 메소드 사용시 오류 발생
=> 타입을 지정하여 컴파일 시점에 안정성을 보장받는 방법 고안(제네릭)
※ 문제발생
=> 제네릭은 불공변
=> 모든 타입에서 공통적으로 사용되는 메소드를 만들 방법이 없음
=> 제네릭 사용전보다 실용성이 떨어져서 와일드카드라는 타입 추가
※ 와일드카드
=> 모든 타입을 대신할 수 있는 와일드카드 타입 (<?>) 추가
=> 정해지지 않은 unknown type이기 때문에 Collection<?>로 선언함으로써 모든 타입에 대해 호출이 가능해짐
=> any type이 아니라 unknown type이라서 문제가 발생
=> Collection의 메소드 등을 사용할 때 값을 추가하기 위해서 제네릭 타입인 E 또는 E의 자식을 넣어줘야하지만 unknown 타입이라 컴파일 에러 발생
※ 위의 문제를 해결하기 위해 한정적 와일드 카드를 사용
=> 특정 타입을 기준으로 상한 범위와 하한 범위를 지정함
=> 상한 경계 와일드카드(Upper Bounded Wildcard)와 하한 경계 와일드카드(Lower Bounded Wildcard)가 존재
class MyGrandParent {
}
class MyParent extends MyGrandParent {
}
class MyChild extends MyParent {
}
※ 상한 경계 와일드카드(Upper Bounded Wildcard)
=> extends를 사용해서 와일드카드 타입의 최상위 타입을 정의
=> 상한 경계를 설정
=> <? extends MyParent>으로 가능한 타입은 MyParent와 미지(unknown)의 모든 MyParent 자식 클래스들
=> MyChild와 AnotherChild (또는 그 외의 타입)
=> 컬렉션 c에서 꺼내서 만들어지는 객체(produce)가 반드시 MyChild 타입이 아닌 AnotherChild가 될 수도 있음, MyChild 타입으로 꺼내려고 시도하면 컴파일 에러 발생
=> MyParent 임은 확실하므로 MyParent와 그 부모 타입으로 꺼내는 것은 문제가 없음
=> 갖고 있는 원소를 사용 또는 소모(consume)하여 컬렉션에 추가하는 경우에는 상황이 달라짐
=> 하위 addElement의 경우 모든 타입에 대해 컴파일 에러가 발생
void printCollection(Collection<? extends MyParent> c) {
// 컴파일 에러
for (MyChild e : c) {
System.out.println(e);
}
for (MyParent e : c) {
System.out.println(e);
}
for (MyGrandParent e : c) {
System.out.println(e);
}
for (Object e : c) {
System.out.println(e);
}
}
void addElement(Collection<? extends MyParent> c) {
c.add(new MyChild()); // 불가능(컴파일 에러)
c.add(new MyParent()); // 불가능(컴파일 에러)
c.add(new MyGrandParent()); // 불가능(컴파일 에러)
c.add(new Object()); // 불가능(컴파일 에러)
}
※ 하한 경계 와일드카드 (Lower Bounded Wildcard)
=> super를 사용해 와일드카드의 최하위 타입을 정의함
=> 하한 경계를 설정
=> <? super MyParent>으로 가능한 타입은 MyParent와 미지의 MyParent 부모 타입들
void addElement(Collection<? super MyParent> c) {
c.add(new MyChild());
c.add(new MyParent());
c.add(new MyGrandParent()); // 불가능(컴파일 에러)
c.add(new Object()); // 불가능(컴파일 에러)
}
void printCollection(Collection<? super MyParent> c) {
// 불가능(컴파일 에러)
for (MyChild e : c) {
System.out.println(e);
}
// 불가능(컴파일 에러)
for (MyParent e : c) {
System.out.println(e);
}
// 불가능(컴파일 에러)
for (MyGrandParent e : c) {
System.out.println(e);
}
for (Object e : c) {
System.out.println(e);
}
}
※ PECS(Producer-Extends, Consumer-Super) 공식
=> 컬렉션으로부터 와일드카드 타입의 객체를 생성 및 만들면(produce) extends 사용
=> 갖고 있는 객체를 컬렉션에 사용 또는 소비(consumer)하면 super를 사용
=> printCollection : 컬렉션으로부터 원소들을 꺼내면서 와일드카드 타입 객체를 생성(produce)
=> addElement : 컬렉션에 해당 타입의 원소를 추가함으로써 객체를 사용(consume)
void printCollection(Collection<? extends MyParent> c) {
for (MyParent e : c) {
System.out.println(e);
}
}
void addElement(Collection<? super MyParent> c) {
c.add(new MyParent());
}