Kotlin의 제네릭: in, out, where
Kotlin의 제네릭은 타입 안전성과 코드 재사용성을 높이는 강력한 기능입니다.
이 가이드에서는 Kotlin 제네릭의 다양한 측면을 상세히 살펴보겠습니다.
제네릭 기본 (Generics Basics)
Kotlin의 클래스는 Java와 마찬가지로 타입 파라미터를 가질 수 있습니다:
class Box<T>(t: T) {
var value = t
}
val box: Box<Int> = Box<Int>(1)
// 또는 타입 추론을 사용하여
val box = Box(1) // Box<Int>로 추론됨
변성 (Variance)
Kotlin은 선언 지점 변성(declaration-site variance)과 타입 프로젝션(type projections)을 제공합니다.
선언 지점 변성 (Declaration-site variance)
out
수정자를 사용하여 공변성(covariance)을 선언할 수 있습니다:
interface Source<out T> {
fun nextT(): T
}
fun demo(strs: Source<String>) {
val objects: Source<Any> = strs // OK, T는 out-파라미터이기 때문
}
in
수정자를 사용하여 반공변성(contravariance)을 선언할 수 있습니다:
interface Comparable<in T> {
operator fun compareTo(other: T): Int
}
fun demo(x: Comparable<Number>) {
x.compareTo(1.0) // 1.0은 Double 타입이며, Number의 하위 타입입니다
val y: Comparable<Double> = x // OK!
}
타입 프로젝션 (Type projections)
사용 지점 변성(use-site variance)을 위해 타입 프로젝션을 사용할 수 있습니다:
fun copy(from: Array<out Any>, to: Array<Any>) {
// ...
}
val ints: Array<Int> = arrayOf(1, 2, 3)
val any = Array<Any>(3) { "" }
copy(ints, any) // OK, Array<out Any>는 Array<Int>의 상위 타입
제네릭 제약 (Generic Constraints)
상한 경계(upper bounds)를 사용하여 타입 파라미터를 제한할 수 있습니다:
fun <T : Comparable<T>> sort(list: List<T>) { ... }
sort(listOf(1, 2, 3)) // OK
sort(listOf(HashMap<Int, String>())) // 오류: HashMap<Int, String>은 Comparable<HashMap<Int, String>>의 하위 타입이 아님
여러 상한 경계가 필요한 경우 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() }
}
타입 소거 (Type Erasure)
Kotlin의 제네릭 타입 정보는 런타임에 소거됩니다. 이는 제네릭 타입의 인스턴스가 실제 타입 인자에 대한 정보를 보유하지 않는다는 것을 의미합니다.
제네릭 타입 검사와 캐스팅 (Generics Type Checks and Casts)
타입 소거로 인해, 일반적으로 제네릭 타입의 인스턴스가 특정 타입 인자로 생성되었는지 런타임에 확인할 수 없습니다. 그러나 star-projected 타입을 사용하여 일부 검사를 수행할 수 있습니다:
if (something is List<*>) {
something.forEach { println(it) } // 항목들은 'Any?' 타입으로 취급됨
}
언체크 캐스트 (Unchecked Casts)
구체적인 타입 인자를 가진 제네릭 타입으로의 캐스트는 런타임에 검사될 수 없습니다:
val intsDictionary: Map<String, Int> = readDictionary(intsFile) as Map<String, Int>
이러한 언체크 캐스트는 경고를 발생시키며, @Suppress("UNCHECKED_CAST")
어노테이션을 사용하여 경고를 억제할 수 있습니다.
타입 인자를 위한 밑줄 연산자 (Underscore Operator for Type Arguments)
밑줄 연산자 _
를 사용하여 타입 인자를 자동으로 추론할 수 있습니다:
val s = Runner.run<SomeImplementation, _>() // T는 String으로 추론됨
val n = Runner.run<OtherImplementation, _>() // T는 Int로 추론됨
Kotlin의 제네릭 시스템은 강력하고 유연합니다. 선언 지점 변성과 사용 지점 변성을 통해 타입 안전성과 유연성 사이의 균형을 잡을 수 있으며, 제네릭 제약을 통해 타입 파라미터의 범위를 명확히 지정할 수 있습니다.
하지만 타입 소거로 인한 제한사항을 이해하고, 언체크 캐스트를 주의해서 사용해야 합니다. 또한 reified 타입 파라미터를 가진 인라인 함수를 활용하면 일부 제한사항을 극복할 수 있습니다.
제네릭을 효과적으로 사용하면 코드의 재사용성을 높이고, 타입 안전성을 향상시킬 수 있습니다. 특히 컬렉션, 함수형 프로그래밍, API 설계 등에서 제네릭의 장점을 크게 활용할 수 있습니다.
Kotlin, 제네릭, 변성, 공변성, 반공변성, 타입프로젝션, 제네릭제약, 타입소거, 언체크캐스트, 타입안전성, 코틀린문법, 안드로이드개발, 서버개발, 함수형프로그래밍, API설계
'개발&프로그래밍' 카테고리의 다른 글
[Kotlin] 열거형 클래스(Enum class) (0) | 2024.08.15 |
---|---|
[Kotlin] 중첩 클래스와 내부 클래스 (0) | 2024.08.15 |
[kotlin] Sealed classes & interfaces (0) | 2024.08.14 |
[kotlin] 데이터 클래스(Data classes) (0) | 2024.08.14 |
[kotlin] 확장 기능(Extensions) (0) | 2024.08.14 |
댓글