English | 简体中文 | 繁體中文 | Русский язык | Français | Español | Português | Deutsch | 日本語 | 한국어 | Italiano | بالعربية
일반은 "파라미터화된 타입"으로, 타입을 파라미터화하여 클래스, 인터페이스, 메서드에 사용할 수 있습니다.
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)과 매우 유사하지만 안전하게 사용할 수 있습니다