1. 무공변성
- 상속 관계에 상관없이 자신의 타입만 허용하는 것을 뜻한다. Kotlin에서는 따로 지정해주지 않으면 기본적으로 모든 Generic Class는 무공변이다. Java에서의
<T>와 같다.
open class Alcohol
class Soju : Alcohol()
interface Drinker<T> {
fun drink()
}
fun varianceTest(input: Drinker<Alcohol>){
input.drink()
}
fun main() {
val alchol: Drinker<Alcohol> = object:Drinker<Alcohol>{
override fun drink(){
println("Drink!")
}
}
val soju: Drinker<Soju> = object:Drinker<Soju>{
override fun drink(){
println("Drink Soju!")
}
}
// Success
println(varianceTest(alchol)) // Drink!
// Error
println(varianceTest(soju)) // Type mismatch: inferred type is Drinker<Soju> but Drinker<Alcohol> was expected
}
- 예시에서
Soju는 Alcohol을 상속받았지만, 별도로 공변성을 지정하지 않았으므로 무공변이다. 따라서 입력으로 Drinker<Alcohol> 타입을 받는 함수 varianceTest에서 Drinker<Soju>를 입력하면 Type mismatch 에러가 발생한다.
2. 공변성
- 자기 자신과 자식 객체를 허용한다. Java에서의
<? extends T>와 같다. Kotlin에서는 out 키워드를 사용해서 이를 표시한다.
open class Alcohol
class Soju : Alcohol()
interface Drinker<T> {
fun drink()
}
fun varianceTest(input: Drinker<out Alcohol>){ // out keyword 추가
input.drink()
}
fun main() {
val alchol: Drinker<Alcohol> = object:Drinker<Alcohol>{
override fun drink(){
println("Drink!")
}
}
val soju: Drinker<Soju> = object:Drinker<Soju>{
override fun drink(){
println("Drink Soju!")
}
}
val any: Drinker<Any> = object:Drinker<Any>{
override fun drink(){
println("Drink Any!")
}
}
// Success
println(varianceTest(alchol)) // Drink!
// Success
println(varianceTest(soju)) // Drink Soju!
// Error
println(varianceTest(any)) //Type mismatch: inferred type is Drinker<Any> but Drinker<out Alcohol> was expected
}
Soju는 Alcohol을 상속받아 구현한 하위 타입이다. varianceTest 함수에서 입력을 Drinker<out Alcohol>로 지정하였으므로 Alcohol과 Alcohol의 하위 타입인 Soju를 모두 입력으로 사용할 수 있다.
- cf.
Soju는 Alcohol의 하위 타입일 때 Drinker<Soju>는 Drinker<Alcohol>의 하위 타입이므로 Drinker는 타입 인자 T에 대해 공변적이다.
- cf. 공변/반공변에 대해 설명하기 위해 작성한 위 코드는 오직 개념 설명만을 위한 예시이다. 실제 코드를 작성할 때에는
in, out 키워드에 대한 개념을 확실히 이해하는 것이 중요하다.
3. 반공병성
- 공변성의 반대 - 자기 자신과 부모 객체만 허용한다. Java에서의
<? super T>와 같다. Kotlin에서는 in 키워드를 사용해서 표현한다.
open class Alcohol
class Soju : Alcohol()
interface Drinker<T> {
fun drink()
}
fun varianceTest(input: Drinker<in Alcohol>){ // in keyword 추가
input.drink()
}
fun main() {
val alchol: Drinker<Alcohol> = object:Drinker<Alcohol>{
override fun drink(){
println("Drink!")
}
}
val soju: Drinker<Soju> = object:Drinker<Soju>{
override fun drink(){
println("Drink Soju!")
}
}
val any: Drinker<Any> = object:Drinker<Any>{
override fun drink(){
println("Drink Any!")
}
}
// Success
println(varianceTest(alchol)) // Drink!
// Error
println(varianceTest(soju)) // Type mismatch: inferred type is Drinker<Soju> but Drinker<in Alcohol> was expected
// Success
println(varianceTest(any)) // Drink Any!
}
varianceTest 함수에서 입력을 Drinker<in Alcohol>로 지정하였으므로 Alcohol과 Alcohol의 상위 타입(예시 코드에서는 별도로 지정된 상위 타입이 없음, Any는 모든 객체의 상위 타입이므로 사용 가능)을 입력으로 사용할 수 있다.