자바에는 Variance(변성)이라는 개념이 있다. 변성은 타입의 계층 관계에서 서로 다른 타입 간에 어떠한 관계가 있는지 나타낸다.
변성의 개념은 자바의 Generic에서도 사용된다. 오늘의 주제인 Covariant(공변), Contravariant(반공변), Invariant(불공변)는 모두 변성의 한 종류이다.
각각의 개념을 간단하게 설명하면 아래와 같다.
Covariant - SubType[]은 SuperType[]이다. (ex - String[]은 Object[]이다.)
Contravariant - SuperType[]은 SubType[]이다. (ex - Object[]은 String[]이다.)
Invariant - SubType[]은 SuperType[]이 아니고, SuperType[]은 SubType[]이 아니다. (ex - String[]은 Object[]이 아니고, Object[]은 String[]이 아니다.)
제목에 쓰여 있듯이 자바에서 배열은 Covariant하고, 제네릭은 Invariant하다. 즉, 객체 타입의 관계에서 A가 B의 하위타입일 때, 배열 A[]는 B[]의 하위 타입이지만 List<A>는 List<B>의 하위 타입이 아니다.
"그렇다면 왜 배열은 Covariant하고, 제네릭은 Invariant할까??"
결론부터 말하자면, 제네릭이 존재하지 않을 당시 배열은 Covariant할 필요가 있었고, 이후에 나온 Generic은 Covariant로 발생하는 문제를 방지하기 위해 Invariant하게 설정되었다.
"배열은 Covariant 할 필요가 있었다."
초기 자바에는 제네릭이라는 개념이 존재하지 않았다. 이 상황에서 배열을 invariant(불변)하게 만드는 것은 다형성 프로그래밍을 방지한다. 예를 들어 Object[] 배열을 비교하는 Object.equals()
함수를 작성할 때 invariant할 경우 배열을 비교할 때 Object가 아닌 정확한 타입(Integer, String, …) 에 대한 equals() 메소드를 각각 작성되어야 할 것이다. 이러한 부분을 해결하기 위해 자바에서는 배열을 Covariant(공변)로 처리한다.
하지만 Covariant함으로써 몇 가지 문제가 발생한다. 자바에서는 배열이 생성될 때 각 타입을 마킹하여 처리한다. 즉, 값이 배열에 저장될 때마다 JVM은 런타임 시 배열의 타입과 런타임 시 값의 타입이 동일한지 확인한다. 만약 array의 타입과 value의 타입이 맞지 않을 경우 ArrayStoreException이 발생한다.
// Covariant - SubType[]은 SuperType[]의 하위타입이다.
Object[] a = new String[1];
// 실제로 a의 타입은 String[] 이기 때문에 런타임 시 ArrayStoreException이 발생한다.
a[0] = new Object();
위와 같이 공변의 경우 ‘쓰기' 시점에 문제가 발생하게 된다. 이 경우 컴파일 시에는 오류를 발견할 수 없고 런타임 시에만 오류를 발견할 수 있으며, 매번 배열에 값을 넣을 때마다 타입 체크가 이루어지기 때문에 성능 저하도 일으키게 된다.
"제네릭은 Invariant하다."
위와 같은 이유들로 인해 JDK 1.5 이후 등장한 제네릭은 공변이 불가능하게 되었고, 대신 와일드카드
를 적용하여 공변과 반공변의 표현이 가능하도록 했다.
'백엔드' 카테고리의 다른 글
[ELK] 1. Kibana에서 Slack Webhook 모니터링 설정하기 (1) | 2023.03.13 |
---|---|
[트러블 슈팅] Reflection 객체 내 @Autowired NullPointerException 발생 (0) | 2022.10.08 |
[Java] Covariant Return Type이란? (0) | 2022.07.15 |
[Java] Collection - 2. ArrayList와 Vector (0) | 2022.07.06 |
[Java] Collection - 1. Collection Framework (0) | 2022.07.05 |
댓글