코틀린 제네릭의 이해와 활용
코틀린은 코드의 타입 안정성을 극대화하기 위해 다양한 기능을 제공합니다. 그중 하나가 바로 **제네릭(Generic)**입니다. 제네릭은 코드를 유연하고 재사용 가능하게 만들면서도 안정성을 유지하는 데 중요한 역할을 합니다. 이 글에서는 코틀린 제네릭의 핵심 개념과 활용법을 체계적으로 정리합니다.
제네릭의 기본 개념: 가변성과 제약 조건
코드를 작성하다 보면 특정 로직을 다양한 타입에서 재사용하려는 요구가 발생합니다. 예를 들어, 파라미터를 모두 Any로 받는 방식으로 일반화를 시도할 수 있습니다. 그러나 이는 타입 안정성을 저하시켜 의도치 않은 오류를 유발할 수 있습니다.
제네릭을 활용하면 다양한 타입에서 재사용 가능한 코드를 작성하면서도, 타입 안정성을 유지할 수 있습니다. 코틀린 컴파일러는 제네릭 클래스 또는 함수가 의도하지 않은 타입에서 사용되지 않도록 철저히 검증합니다.
타입 불변성과 제네릭
코틀린은 기본적으로 **타입 불변성(invariance)**을 적용합니다. 이를테면, Array<Car>는 Array<Vehicle>로 간주되지 않습니다. 다음 예제를 통해 확인해 보겠습니다:
open class Vehicle(val type: String)
class Car : Vehicle("Car")
class Bike : Vehicle("Bike")
fun processVehicles(vehicles: Array<Vehicle>) {
println("Number of vehicles: ${vehicles.size}")
vehicles.forEach { println(it.type) }
}
fun main() {
val cars = arrayOf(Car(), Car())
processVehicles(cars) // 오류 발생
}
왜 오류가 발생할까요?
processVehicles 함수는 Array<Vehicle>를 요구하지만, Array<Car>는 이를 만족하지 못합니다. 이는 타입 안정성을 보장하기 위한 코틀린의 설계 때문입니다. 만약 위 코드가 허용된다면, 함수 내부에서 vehicles[0] = Bike()와 같은 코드가 실행될 수 있으며, 이는 런타임 오류를 유발할 수 있습니다.
불변(immutable)과 공변성(covariance)
다음 예제를 살펴보겠습니다.
fun processVehicles(vehicles: List<Vehicle>) {
println("Number of vehicles: ${vehicles.size}")
vehicles.forEach { println(it.type) }
}
fun main() {
val cars: List<Car> = listOf(Car(), Car())
processVehicles(cars) // 정상 동작
}
Array와 달리 List에서는 List<Car>를 List<Vehicle>로 전달할 수 있습니다. 이는 List가 **공변성(covariance)**을 지원하기 때문입니다. List는 다음과 같이 정의되어 있습니다:
interface List<out T>
out 키워드는 읽기 전용(read-only) 제약을 명시하며, 이는 공변성을 허용합니다.
공변성(out)과 반공변성(in)
코틀린에서는 타입 안정성을 유지하면서도 유연성을 확보하기 위해 out과 in 키워드를 제공합니다.
공변성 (out T)
공변성은 제네릭 타입이 **파생 타입(subtype)**에 대해 안전하게 동작할 수 있도록 보장합니다. 다음은 공변성을 활용한 예제입니다.
fun copyFromTo(from: Array<out Vehicle>, to: Array<Vehicle>) {
for (i in from.indices) {
to[i] = from[i]
}
}
fun main() {
val cars = arrayOf(Car(), Car())
val vehicles = Array<Vehicle>(2) { Vehicle("Unknown") }
copyFromTo(cars, vehicles)
vehicles.forEach { println(it.type) } // Car, Car
}
from 파라미터는 out 키워드를 통해 읽기 전용으로 설정되었기 때문에, 컴파일러는 데이터 추가가 없음을 보장합니다.
반공변성 (in T)
반대로, **반공변성(contravariance)**은 **상위 타입(supertype)**에 대해 안전한 동작을 보장합니다. 다음 예제를 살펴보겠습니다.
fun addVehicles(vehicles: Array<Vehicle>, destination: Array<in Vehicle>) {
for (i in vehicles.indices) {
destination[i] = vehicles[i]
}
}
fun main() {
val vehicles = arrayOf(Vehicle("Truck"), Vehicle("Bus"))
val garage = Array<Any>(2) { "Empty Slot" }
addVehicles(vehicles, garage)
garage.forEach { println(it) } // Vehicle: Truck, Vehicle: Bus
}
destination 파라미터는 in 키워드를 통해 **쓰기 전용(write-only)**으로 설정되었기 때문에, 데이터를 안전하게 삽입할 수 있습니다.
제네릭 타입 제약 조건: where 키워드
제네릭 타입에 제약 조건을 추가하여 특정 타입만 허용할 수 있습니다. 다음 예제를 통해 살펴보겠습니다:
fun <T : AutoCloseable> manageResource(resource: T) {
resource.close() // AutoCloseable을 구현한 타입만 허용
}
class NetworkConnection : AutoCloseable {
override fun close() {
println("Connection closed")
}
}
fun main() {
val connection = NetworkConnection()
manageResource(connection) // Connection closed
}
여러 제약 조건이 필요한 경우 where 키워드를 사용합니다.
fun <T> processResource(resource: T) where T : AutoCloseable, T : Appendable {
resource.append("Processing complete")
resource.close()
}
스타 프로젝션(*): 안전한 타입 처리
스타 프로젝션은 타입 안정성을 유지하면서도 타입 정보를 명시하지 않을 때 사용합니다.
fun displayItems(items: Array<*>) {
for (item in items) {
println(item)
}
}
fun main() {
val mixedArray: Array<Any> = arrayOf("String", 42, 3.14)
displayItems(mixedArray) // String, 42, 3.14
}
스타 프로젝션은 읽기 전용으로 동작하며, 타입 불변성을 유지합니다.
구체화된 타입 파라미터(reified)
코틀린은 reified 키워드를 통해 런타임에 타입 정보를 사용할 수 있도록 지원합니다. 이는 자바의 Class<T>를 사용하는 방식보다 간결하고 안전합니다.
inline fun <reified T> findFirst(items: List<Any>): T? {
return items.filterIsInstance<T>().firstOrNull()
}
fun main() {
val items = listOf(Any(), Car(), Bike())
val firstCar: Car? = findFirst(items)
println(firstCar?.type) // Car
}
마무리
코틀린의 제네릭은 코드의 안정성과 유연성을 동시에 제공하는 강력한 기능입니다. 제네릭의 개념과 활용법을 체계적으로 익히면, 보다 견고하고 재사용 가능한 코드를 작성할 수 있습니다.
'코틀린' 카테고리의 다른 글
컴포즈 UI 성능과 Stability (1) | 2025.01.18 |
---|---|
Sealed Class와 Enum Class의 차이점 (0) | 2025.01.15 |
불변 객체 (0) | 2022.12.14 |
inline 함수 (0) | 2022.08.31 |
코틀린 by란? (0) | 2022.06.28 |