# 제네릭의 공변, 반공변, 무공변
# 무공변
- 자바에서 제네릭은 기본적으로 무공변(Invariant)임
- 무공변 : 타입 S, T가 있을 때 서로 관계가 없다는 것을 의미
public class Animal {
}
public class Cat extends Animal {
}
List<Animal> animals = new ArrayList<Cat>(); // 컴파일 에러
List<Cat> cats = new ArrayList<Animal>(); // 컴파일 에러
- 위와 같이
Animal과Cat이 서로 상속 관계이기 때문에 공변성이 있지만, 제네릭은 상속 관계가 호환되지 않음(무공변) - 즉, 타입이 정확하게 일치하지 않으면 컴파일 에러 발생
- 무공변은 타입 안정성을 보장하지만, 유연성이 부족하다는 단점이 있음
- 자바에서는 와일드카드(?)와 extends, super 키워드로 공변과 반공변을 지원함
# 공변
- S가 T의 하위 타입일 때 S는 T가 될 수 있다는 것을 의미
public class Main {
public static void main(String[] args) {
List<? extends Animal> animals = new ArrayList<Dog>();
animals.add(null); // 가능
animals.add(new Dog()); // 불가능
}
public static class Animal {
}
public static class Dog extends Animal {
}
}
- 제네릭에서는
<? extends T>를 사용하여 하위 타입을 허용하고 읽기 전용으로 사용 가능 - 쓰기는 null만 가능 → 런타임 시점에 어떤 타입이 올지 모르기 때문에 어떤 구체적인 객체도 추가를 허용하지 않음
# 반공변
- S가 T의 하위 타입일 때 T는 S가 될 수 있다는 것을 의미
public class Main {
public static void main(String[] args) {
List<? super Dog> animals = new ArrayList<Dog>();
animals.add(new Dog());
Object object = animals.get(0);
}
public static class Animal {
}
public static class Dog extends Animal {
}
}
- 제네릭에서는
<? super S>를 사용하여 상위 타입을 허용하고 쓰기 전용으로 사용 가능 - 읽기는 Object 타입으로만 가능 → Dog, Animal, Object 중 어떤 타입일지 알 수 없기 때문에 최상위 타입으로만 반환
# PECS란?
- PECS(Producer Extends, Consumer Super)는 제네릭에서 와일드카드의 상위 또는 하위 경계를 설정할 때 사용되는 가이드라인
- 객체를 생산할 때는
<? extends T>를 사용하고, 소비할 때는<? super T>를 사용
public void produce(List<? extends Animal> animals) { // 생산자 역할
for (Animal animal : animals) {
System.out.println("animal = " + animal);
}
}
public void consume(List<? super Dog> dogs) { // 소비자 역할
dogs.add(new Dog());
}
# <?>와 <Object>의 차이점
# <?>
- 모든 타입을 메서드 인자로 받을 수 있지만 null 외에는 값을 추가할 수 없기 때문에 읽기 전용으로 사용됨
# <Object>
- <Object> 외의 타입을 메서드 인자로 받을 수 없지만 모든 객체를 추가할 수 있기 때문에 읽기, 쓰기 모두 사용 가능함