서론

코틀린의 제네릭과 관련된 개념 중 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

/**
* created by victory_woo on 2020/06/25
* */

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) // error
test(jvm) // success
test(kotlin) // error

}

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) // error
test(jvm) // success
test(kotlin) // success
}

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

/**
* created by victory_woo on 2020/06/25
* */

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) // success
test(jvm) // success
test(kotlin) // error

}

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