
공변성이란 타입 간 계층관계(상속)가 같은 방향으로 적용되는 것을 의미한다.
예를 들어 S 가 T 의 하위 타입이면,
S[] 는 T[] 의 하위 타입이다.
List<S> 는 List<T> 의 하위 타입이다.
이러한 공변성은 타입 간의 상속 관계를 복합 타입에서도 유지할 수 있게 해준다. 이를 통해 더 유연하고 재사용 가능한 코드를 작성할 수 있다.
자바의 배열은 공변성을 지원한다. 즉, String이 Object의 서브타입이라면, String[]은 Object[]의 서브타입이 된다.
✍️ 작성
public class ex1 {
public static void main(String[] args) {
String[] stringArray = {"Hello", "World"};
Object[] objectArray = stringArray; // 가능: String[]은 Object[]의 서브타입
objectArray[0] = "Hi";
objectArray[1] = 42; // 런타임 오류: ArrayStoreException 발생
}
}
String[]이 Object[]의 서브타입이므로, String[]을 Object[]타입으로 참조할 수 있음을 확인할 수 있다.
그러나 objectArray를 통해 Integer를 추가하려고 하면, 실제 배열은 String[]이므로 ArrayStoreException이 발생한다.
이는 공변성의 단점으로, 배열이 타입 안전성을 보장하지 못한다.
지네릭에서는 공변성을 지원하지 않지만, 와일드카드(?)를 사용하여 간접적으로 공변성을 구현할 수 있다. 와일드카드는 지네릭 타입의 불특정한 타입 매개변수를 표현한다.
✍️ 작성
public class ex1 {
public static void main(String[] args) {
List<? extends Number> numberList = new ArrayList<Integer>();
Number num = numberList.get(0); // 가능 --> 실제로는 numberList가 비어있어 컴파일 오류
}
}
다음 코드에서 쓰인 공변적 와일드카드 (<? extends T>)는 T 타입과 그 서브타입들을 허용한다.
반공변성이란 타입 간 계층관계가 반대 방향으로 적용되는 것을 의미한다.
예를 들어 S 가 T 의 하위 타입이면,
T[] 는 S[] 의 하위 타입이다. (공변성의 반대)
List<T> 는 List<S> 의 하위 타입이다. (공변성의 반대)
지네릭에서는 반공변성을 지원하지 않지만, 와일드카드(?)를 사용하여 간접적으로 반공변성을 구현할 수 있다. 와일드카드는 지네릭 타입의 불특정한 타입 매개변수를 표현한다.
✍️ 작성
import java.util.ArrayList;
import java.util.List;
public class ContravarianceExample {
public static void main(String[] args) {
// List<? super Integer>은 Integer와 그 슈퍼타입(Object)을 허용
List<? super Integer> superNumberList = new ArrayList<Object>();
// 요소 추가가 가능
superNumberList.add(10); // Integer 추가
superNumberList.add(20); // Integer 추가
// superNumberList.add("Hello"); // 컴파일 오류: String은 Integer의 슈퍼타입이 아님
// 요소 읽기: Object 타입으로 반환
Object obj1 = superNumberList.get(0);
Object obj2 = superNumberList.get(1);
System.out.println("첫 번째 요소: " + obj1);
System.out.println("두 번째 요소: " + obj2);
}
}
🖥️ 결과
첫 번째 요소: 10
두 번째 요소: 20
superNumberList는 Integer와 그 슈퍼타입인 Object를 포함할 수 있는 리스트를 참조한다.
자바의 제네릭은 불변성(Invariance)을 기본으로 한다. 이는 제네릭 타입의 상속 관계가 타입 매개변수의 상속 관계에 영향을 받지 않는다는 것을 의미한다. 즉, List<String>은 List<Object>의 상위 타입도, 하위 타입도 아니다.
이러한 불변성은 타입 매개변수의 상속 관계 무시를 무시하며 타입 안전성을 보장하고 유연성을 제한한다.
✍️ 작성
import java.util.ArrayList;
import java.util.List;
public class InvarianceExample {
public static void main(String[] args) {
List<String> stringList = new ArrayList<>();
stringList.add("Hello");
stringList.add("World");
// List<Object> objectList = stringList; // 컴파일 오류: 불변성으로 인해 호환되지 않음
// 해결 방법: 와일드카드 사용
List<? extends Object> wildcardList = stringList; // 가능
Object obj1 = wildcardList.get(0); // 가능
Object obj2 = wildcardList.get(1); // 가능
System.out.println("첫 번째 요소: " + obj1);
System.out.println("두 번째 요소: " + obj2);
// 요소 추가는 불가능
// wildcardList.add("Java"); // 컴파일 오류
}
}
🖥️ 결과
첫 번째 요소: Hello
두 번째 요소: World
List<String>은 List<Object>의 상위 타입도, 하위 타입도 아니므로 직접적으로 할당할 수 없다.
자바 제네릭 불공변 / 공변 / 반공변 처음 들어봅니다.
자바에서 공변성(covariance), 반공변성(contravariance) 그리고 무변성 (invariance)
Java 배열과 리스트의 공변성과 반공변성, 무공변성
☕ 자바 제네릭의 공변성 & 와일드카드 완벽 이해