자바나 코틀린에서 제네릭을 다룰 때는 변성이라는 개념이 중요합니다.
제네릭에서의 상위 타입과 하위 타입은 일반적인 개념과 조금 다르기 때문입니다.
자바에서는 사용 지점 변성 (use-site variance)
만 가능한 반면에,
코틀린에서는 변성을 계속 지정해야 하는 사용 지점 변성의 단점을 개선하기 위해서
선언 지점 변성 (declaration site variance)
을 통해
변성을 지정할 수 있도록 지원합니다.
물론 사용 지점 변성도 가능합니다.
변성은 제네릭 타입 간의 상위 타입과 하위 타입을 구분 짓기 위한 개념입니다.
일반적으로 어떠한 타입 T
는 Any?
타입의 하위 타입이라는 것을 알 수 있습니다.
하지만 List<T>
타입이 List<Any?>
타입의 하위 타입이라는 것은 확신할 수 없습니다.
왜요?
만약 다음과 같은 함수가 있다고 생각해봅시다.
fun addString(targetList: MutableList<Any`>`) { targetList.add("String") }
만약
MutableList<Integer>
타입이MutableList<Any>
의 하위 타입이라면
위addString()
메소드에 인자로MutableList<Integer>
타입의 변수를
보낼 수 있을 것입니다.그러면
Integer
타입만 저장할 수 있는 리스트에String
타입을 저장함으로써
타입 불일치 에러가 발생하게 될 것입니다.
그래서 어떤 제네릭 타입이 다른 어떠한 제네릭 타입의 상위 타입인지 하위 타입인지를
구분할 수 있도록 지정해주는 것이 변성이라는 개념입니다.
자바에서는 변성을 사용 지점 변성을 이용하여 지정합니다.
자바에서는 클래스 내부의 메소드에 제네릭을 적용할 때마다 타입의 변성을 지정합니다.
어떠한 타입 T
의 공변성을 허용하려면 <? extend T>
를 사용하고
어떠한 타입 T
의 반공변성을 허용하려면 <? super T>
를 사용합니다.
공변성이란 어떠한 타입
T
에 대해 그 타입과 그의 서브 타입을 허용하는 것을 말합니다.
반공변성은 어떠한 타입T
에 대해 그 타입과 그의 수퍼 타입을 허용하는 것을 말합니다.
코틀린에서는 사용 지점 변성을 out
과 in
키워드를 이용해 지원합니다.
자바에서의 <? extend T>
가 코틀린에서는 <out T>
이고,
자바에서의 <? super T>
가 코틀린에서는 <in T>
입니다.
만약 자바에서 제공하는 사용 지점 변성을 사용하여 모든 메소드에 공변성을 적용하려면
공변성이 적용되는 제네릭 타입을 사용하는 모든 메소드에 위 키워드들을 작성해야 합니다.
이러한 불편함을 해결하고 in 위치
와 out 위치
라는 개념을 통해
더 안전하고 쉬운 개발을 할 수 있도록 도와줍니다.
선언 지점 변성은 클래스를 선언할 때 공변성을 지정할 수 있게 하는 기능입니다.
class Box<out T>(val data: T)
이런 간단한 클래스가 있다고 했을 때
여기서 <out T>
는 사용 지점 변성이 아니라
클래스 전역에 설정하는 선언 지점 변성입니다.
그래서 T
타입은 out 위치
에서만 사용할 수 있도록 제한합니다.
out 위치
는 보통 반환할 때의 위치 즉, 반환 타입을 말하는데,
반환 타입 말고도out 위치
를 특정하는 곳이 있습니다.
정확한 정보는 코틀린 공식 문서를 참조해주세요.
val box: Box<Number> = Box(10)
이렇게 생성한 Box
객체는 Number
의 하위 타입인
Int
, Double
타입만 가질 수 있습니다.
이는 클래스를 객체로 만들어 사용할 때도 적용할 수 있습니다.
MutableList
는 add()
메소드처럼 in 위치
에 타입 파라미터가 있는 메소드도 있고,
get()
메소드처럼 out 위치
에 타입 파라미터가 있는 메소드도 있습니다.
val mutableList: MutableList<out String> = mutableListOf("a", "b")
/*
mutableList.add("c")
Type mismatch. Required: Nothing Found: String
*/
println(mutableList[0])
이렇게 out 위치
에 타입 파라미터가 있는 메소드는 사용할 수 있지만,
in 위치
에 타입 파라미터가 있는 메소드는 사용할 수 없고
타입 파라미터 대신에 Nothing
으로 치환됩니다.
코틀린에서는 자바에서 지원하지 않는 선언 지점 변성이라는 기능을 지원합니다.
이 선언 지점 변성을 이용하게 되면 더 안전한 제네릭 프로그래밍을 할 수 있을 것 같습니다.