English | 简体中文 | 繁體中文 | Русский язык | Français | Español | Português | Deutsch | 日本語 | 한국어 | Italiano | بالعربية

Kotlin 일반화

일반은 "파라미터화된 타입"으로, 타입을 파라미터화하여 클래스, 인터페이스, 메서드에 사용할 수 있습니다.

Java와 마찬가지로 Kotlin도 일반을 제공하며, 타입 안전을 보장하고 타입 강제 변환의 번거로움을 해결합니다.

일반 클래스를 선언합니다:

class Box<T>(t: T) {
    var value = t
}

클래스 예제를 생성할 때는 타입 파라미터를 지정해야 합니다:

val box: Box<Int> = Box<Int>(1)
// 또는
val box = Box(1) // 컴파일러는 타입 추론을 수행합니다1 타입 Int이므로 컴파일러는 우리가 Box<Int>을 말하는 것을 알고 있습니다.

다음 예제는 일반 클래스 Box에 정수형 데이터와 문자열을 전달합니다:

class Box<T>(t: T) {
    var value = t
}
fun main(args: Array<String>) {
    var boxInt = Box<Int>(10)
    var boxString = Box<String>("w3codebox")
    println(boxInt.value)
    println(boxString.value)
}

출력 결과는 다음과 같습니다:

10
w3codebox

일반 타입 변수를 정의할 때는 타입 파라미터를 완전히 작성할 수 있으며, 컴파일러가 자동으로 타입 파라미터를 추론할 수 있으면 생략할 수도 있습니다.

Kotlin 일반 함수의 선언은 Java와 동일하며, 타입 파라미터는 함수 이름 앞에 위치합니다:

fun <T> boxIn(value: T) = Box(value)
// 다음 모두가 유효한 문장입니다
val box =4 val box = boxIn<Int>(1)
val box =5 = boxIn(1)     // 컴파일러는 타입 추론을 수행합니다

일반 함수 호출 시 타입 파라미터를 추론할 수 있으면 일반 파라미터를 생략할 수 있습니다.

다음 예제는 일반 함수 doPrintln을 생성합니다. 함수는 전달된 다른 타입에 따라 적절한 처리를 합니다:

fun main(args: Array<String>) {
    val age = 23
    val name = "w3codebox"
    val bool = true
    doPrintln(age)    // 정수형
    doPrintln(name)   // 문자열
    doPrintln(bool)   // 부울형
}
fun <T> doPrintln(content: T) {
    when (content) {
        is Int -> println("정수형 숫자는 $content")
        is String -> println("문자열을 대문자로 변환합니다:${content.toUpperCase()}")
        else -> println("T는 정수도 아니고 문자열도 아닙니다")
    }
}

출력 결과는 다음과 같습니다:

정수형 숫자는 23
문자열을 대문자로 변환합니다: w3codebox
T는 정수도 아니고 문자열도 아닙니다

제네릭 제한

제네릭 제한을 사용하여 특정 매개변수가 사용할 수 있는 타입을 설정할 수 있습니다.

Kotlin에서는 :을 사용하여 제네릭의 타입 상한을 제한합니다.

가장 일반적인 제한은 상한(upper bound)입니다:

fun <T : Comparable<T>> sort(list: List<T>) {
    // ……
}

Comparable의 서브 타입은 T 대신 사용할 수 있습니다. 예를 들어:

sort(listOf(1, 2, 3)) // OK. Int는 Comparable<Int>의 서브 타입입니다
sort(listOf(HashMap<Int, String>())) // 에러: HashMap<Int, String>는 Comparable<HashMap<Int, String>>의 서브 타입이 아닙니다

기본 상한은 Any?입니다.

여러 상한 제한 조건이 있을 때, where 문을 사용할 수 있습니다:

fun <T> copyWhenGreater(list: List<T>, threshold: T): List<String>
    where T : CharSequence,
          T : Comparable<T> {
    return list.filter { it > threshold }.map { it.toString() }
}

타입 변환

Kotlin에는 와이드 카테고리 타입이 없으며, 다른 두 가지가 있습니다: 선언 위치 타입 변환(declaration-site variance)과 타입 프로젝션(type projections)입니다.

선언 위치 타입 변환

선언 위치의 타입 변환은 콘베르전스 어노테이션 메타데이터: in, out, 소비자 in, 제공자 out를 사용하여 표시합니다.

out를 사용하여 타입 파라미터가 콘베르전스를 가질 수 있습니다. 콘베르전스 타입 파라미터는 출력으로만 사용할 수 있으며, 반환 타입으로 사용할 수 있지만 입력 타입으로 사용할 수 없습니다:

// 정의하려는 클래스는 콘베르전스를 지원합니다
class w3codebox<out A>(val a: A) {
    fun foo(): A {
        return a
    }
}
fun main(args: Array<String>) {
    var strCo: w3codebox<String> = w3codebox("a")
    var anyCo: w3codebox<Any> = w3codebox<Any>("b")
    anyCo = strCo
    println(anyCo.foo())   // 输出 a
}

in 使得一个类型参数逆变,逆变类型参数只能用作输入,可以作为入参的类型但是无法作为返回值的类型:

// 定义一个支持逆变的类
class w3codebox<in A>(a: A) {
    fun foo(a: A) {
    }
}
fun main(args: Array<String>) {
    var strDCo = w3codebox("a")
    var anyDCo = w3codebox<Any>("b")
    strDCo = anyDCo
}

星号投射

有些时候, 你可能想表示你并不知道类型参数的任何信息, 但是仍然希望能够安全地使用它. 这里所谓"安全地使用"是指, 对泛型类型定义一个类型投射, 要求这个泛型类型的所有的实体示例, 都是这个投射的子类型。

对于这个问题, Kotlin 提供了一种语法, 称为 星号投射(star-projection):

  • 假如类型定义为 Foo<out T> , 其中 T 是一个协变的类型参数, 上界(upper bound)为 TUpper ,Foo<> 等价于 Foo<out TUpper> . 它表示, 当 T 未知时, 你可以安全地从 Foo<> 中 读取TUpper 类型的值.

  • 假如类型定义为 Foo<in T> , 其中 T 是一个反向协变的类型参数, Foo<> 等价于 Foo<inNothing> . 它表示, 当 T 未知时, 你不能安全地向 Foo<> 写入 任何东西.

  • 假如类型定义为 Foo<T> , 其中 T 是一个协变的类型参数, 上界(upper bound)为 TUpper , 对于读取值的场合, Foo<*> 等价于 Foo<out TUpper> , 对于写入值的场合, 等价于 Foo<in Nothing> .

如果一个泛型类型中存在多个类型参数, 那么每个类型参数都可以单独的投射. 比如, 如果类型定义为interface Function<in T, out U> , 那么可以出现以下几种星号投射:

  • Function<*, String> , Function<Nothing, String>을 의미합니다;

  • Function<Int, *> , Function<Int, Any?>을 의미합니다;

  • Function<, > , Function<Nothing, Any?>을 의미합니다.

주의: 별표投射은 Java의 원시 타입(raw type)과 매우 유사하지만 안전하게 사용할 수 있습니다