서론
코틀린의 제네릭과 관련된 개념 중 in
, out
키워드의 정확한 의미를 몰라 정리하게 되었다.
본론
- 모던 랭귀지들은 타입바운드 개념을 제공한다.
- 타입 바운드는 다음과 같이 3가지로 분류할 수 있다.
1. 무공변성(invariant)
2. 공변성(convariant)
3. 반공변성(contravariant)
1. 무공변성(invariant)
- 상속 관계에 상관없이, 자기 타입만을 허용하는 것을 의미한다.
- 보기 편하도록 한 파일 안에 필요한 클래스와 인터페이스를 작성했다.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44
| package GenericsSample.invariantTest
fun main(args: Array<String>) { val language : Some<Language> = object : Some<Language>{ override fun something() { println("language") } }
val jvm : Some<JVM> = object : Some<JVM>{ override fun something() { println("jvm") } }
val kotlin : Some<Kotlin> = object : Some<Kotlin>{ override fun something() { println("kotlin") } }
test(language) test(jvm) test(kotlin)
}
fun test(value: Some<JVM>){ println("Success") }
interface Some<T> { fun something() }
open class Language
open class JVM : Language()
class Kotlin : JVM()
|
- test 함수는 Some 타입만을 입력받는다. 따라서 Language, Kotlin은 허용하지 않는다. 이러한 개념은
무공변성
이라고 한다.
2. 공변성(convariant)
- 타입 생성자에게 리스코프 치환 법칙을 허용한다는 의미이다.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37
| fun main(args: Array<String>){ val language : Some<Language> = object : Some<Language>{ override fun something() { println("language") } }
val jvm : Some<JVM> = object : Some<JVM>{ override fun something() { println("jvm") } }
val kotlin : Some<Kotlin> = object : Some<Kotlin>{ override fun something() { println("kotlin") } }
test(language) test(jvm) test(kotlin) }
fun test(value: Some<out JVM>){ println("Success") }
interface Some<T> { fun something() }
open class Language
open class JVM : Language()
class Kotlin : JVM()
|
- test 함수의 JVM 앞에 out 키워드를 추가했다.
out
: 자기 자신과 자식 객체를 허용하겠다는 의미이다.
- 따라서 JVM을 상속받은 Kotlin을 타입으로 사용하는 kotlin은 error가 발생하지 않는다.
3. 반공변성(contravariant)
- 공변성의 반대 개념을 생각하면 쉽다.
- 자기 자신과 부모 객체만 허용한다.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49
| package GenericsSample.contravariantTest
import GenericsSample.invariantTest.JVM import GenericsSample.invariantTest.Kotlin import GenericsSample.invariantTest.Language import GenericsSample.invariantTest.Some
fun main(args: Array<String>) { val language: Some<Language> = object : Some<Language> { override fun something() { println("language") } } « val jvm: Some<JVM> = object : Some<JVM> { override fun something() { println("jvm") } }
val kotlin: Some<Kotlin> = object : Some<Kotlin> { override fun something() { println("kotlin") } }
test(language) test(jvm) test(kotlin)
}
fun test(value: Some<in JVM>) { println("Success") }
interface Some<T> { fun something() }
open class Language
open class JVM : Language()
class Kotlin : JVM()
|
- test 함수의 JVM 앞에 in 키워드를 추가했다.
in
: 자기 자신과 부모 객체만을 허용한다는 의미이다.
- 따라서 JVM과 부모인 Language는 컴파일이 되지만, JVM을 상속받은 Kotlin 객체는 허용하지 않는다.
결론
- 무공변성은 타입 앞에 아무런 키워드가 붙지 않으며, 해당 타입의 객체 즉, 자신만을 허용한다.
- 공변성은 out 키워드를 사용하며, 자기 자신과 자기 자신을 상속받은 즉, 자식 객체 타입을 허용한다.
- 반공변성은 in 키워드를 사용하며, 자기 자신과 자기 자신의 슈퍼 타입 즉, 부모 객체 타입을 허용한다.
Reference