자바의 변성 알아보기
목차
변성이란 다형성을 구현하기 위해 나온 개념이다. 다형성을 구현하는 건 항상 좋은 걸까?
변성에 대해 자세히 알아보자. 그리고 자바는 변성을 가지고 있을 까❔
변성은 왜 사용할까?
변성이란 서로 다른 타입 간에 어떤 관계가 있는지 나타내는 개념이다.
초기화 시, 혹은 매개변수가 T로 선언되어있다면
공변 : T와 T의 자식타입만 받을 수 있다.
반공변 : T와 T의 부모타입만 받을 수 있다.
불공변 : T만 가능하다(선언한 타입만 들어갈 수 있음).
이러한 변성은 왜 사용할까? 다형성을 구현할 수 있기에 쓴다. 자기 자신외의 타입들도 받을 수 있기 때문이다. 하지만 다형성이 있다면 타입 안정성이 떨어져 런타임 에러가 발생할 수 있다.
타입 안정성이란 ❔
의도하지 않는 타입의 객체가 저장되는 것을 막고, 저장된 객체를 꺼내올 때 원래의 타입과 다른 타입으로 잘못 형변환되어 발생할 수 있는 오류를 줄어주는 것. ⇒ 아래 예시가 나온다.
자바는 변성을 가질까?
그렇다면 자바는 변성을 가질까? 배열/제네릭/한정적 와일드 카드의 케이스마다 다르다. 각각의 경우마다 변성과 타입 안정성을 알아보자.
우선 배열은 공변성을 가진다. 단, 업캐스팅 할 경우 잘못된 타입을 삽입할 수 있다. 아래 예시에서는 선언부를 Object
로 하고, 구현체는 Long
으로 하였다. 이때, 실제 구현체(Long
)와 다른 자식(String
)을 삽입할 경우, 런타임 에러가 발생한다. 즉, 배열을 업캐스팅하면 다형성을 구현할 수 있다. 하지만 타입 안정성이 떨어져 런타임 에러가 발생할 수 있다.
반면 제네릭은 변성을 가지지 않는다(불공변성). 오직 자기자신과 같은 타입만 가능하다. 따라서 제네릭은 배열처럼 잘못된 형 삽입으로 인한 런타임 에러가 발생하지는 않는다. 그러나 다형성을 구현할 수는 없다.
자바의 변성을 정리해보면, 배열은 공변성을 가지면서 다형성을 구현한다. 그러나 잘못된 형 삽입으로 인해 런타임 에러가 발생할 수 있다(타입 안정성이 없다). 제네릭은 공변성을 가지지 않는다(자기 타입만 가능). 타입 안정성을 가져 인한 런타임 에러가 발생할 일은 없으나, 다형성을 구현하지 못한다. 그렇다면 제너릭의 한정된 와일드 카드의 경우는 어떨까❔
🚩 배열과 제네릭을 보면, 다형성과 타입 안정성을 함께 가지지는 못하는 듯 보인다.
제네릭이 한정적 와일드 카드를 사용하면 공변성 혹은 반공변성을 가진다.
이때도 다형성과 타입 안정성을 둘 다 가지지 못할까?
공변성(Co-variant)
<? extend T>
: T와 T의 자식 객체만 가능자기 자신과 자식 객체만 가능하다.
타입 매개변수에 <? extends Type>
를 선언하면 타입 매개변수가 공변성을 가진다. Type
자신과 자식 클래스만 가능하다. 제네릭으로 업캐스팅할 경우(공변성), 읽기는 가능하지만 쓰기는 불가능하다. 쓰기(삽입하기)를 하면 컴파일 에러가 발생한다. 따라서 타입 안정성을 가지고, 런타임 에러를 막는다.
반대로 쓰기가 필요하고, 읽을 필요는 없을 수 있다. 이때는 <? super T>
(반공변성)을 사용한다.
반공변성(Contra-variance)
<? super T>
: T와 T의 부모 타입만 가능자기 자신과 부모 타입만 가능하다.
타입 매개변수에 <? super Type>
를 선언하면 타입 매개변수에 반공변성을 가진다. 즉, Type
의 부모들을 받을 수 있다. 이때는 쓰기를 해도 런타임 에러가 발생하지 않는다. 부모 객체들을 받는 데, 부모는 자식보다 한정된 기능을 가지고 있기 때문이다. 반면 읽기는 안된다. 읽기(꺼내오기)를 하면 컴파일 에러가 발생한다.
부모 타입 선언 = 자식 타입 구현체
⇒ 업캐스팅, 가능하다자식 타입 선언 = 부모 타입 구현체
⇒ 불가능하다. 자식(부모+a)이기 때문이다. 부모 구현체로 자식의 메소드를 사용할 수 없기 때문이다.읽으면(꺼내면) 이 경우에 해당된다.
참고) 한정적 와일드카드 타입 사용시, 변성을 정하는 지점이 2가지 있다.
선언 지점 변성 : 클래스 선언 시 변성을 정함
List<? extends T> x = new ArrayList<Dog>();
사용 지점 변성 : 타입 파라미터에 변성을 정함
public void func(List<? extends T> x)
정리하면
공변은 다형성을 구현할 수 있으나, 타입 안정성이 없어 런타임 에러가 날 수 있다.
자바의 배열은 공변성이다. 다형성을 구현할 수 있지만, 타입 안정성을 가지지 못한다.
자바의 제너릭은 불공변성이다. 타입 안정성을 가지나, 다형성을 구현하지 못한다.
제너릭 한정적 와일드 카드의 경우
<? extend T>
: 공변성을 가지고, 읽기O, 쓰기X(컴파일 에러)<? super T>
: 반공변성을 가지고, 쓰기O, 읽기X(컴파일 에러)케이스마다 공변성/반공변성을 선택하여 사용한다. 이로 인해 다형성과 타입 안정성을 둘 다 가지면서 구현할 수 있다.
Reference
Last updated