Generic
07 Apr 2018Generic
클래스와 인터페이스의 매개변수 또는 함수의 매개변수와 반환 타입을 미리 확정하지 않고 정의한 후에 사용되는 시점에서 특정 타입을 지정할 수 있도록 해주는 기법
제네릭 클래스의 인스턴스 타입
원시 타입과 제네릭 타입이 결합된 것이 자신의 타입이 된다.
// List<Int> 타입
val listOfInts: List<Int> = listOf(1, 2, 3)
// List<Room> 타입
val listOfRooms: List<Room> = listOf(Room("room1"), Room("room2"))
제네릭 타입 매개변수의 표준 명칭
- E: 컬렉션에 저장되는 요소(Entity)
- K: 키, Map<K, V>
- V: 값
- N: 숫자 타입
- T: 모든 타입, 기본적으로 Any?를 의미한다.
- R: 함수의 반환값을 나타낸다.
제네릭 타입 정의 및 사용
Generic 클래스 생성하기
class LootBox<T>(item: T) {
private var loot: T = item
}
class Fedora(val name: String, val value: Int)
class Coin(val value: Int)
fun main(args: Array<String>) {
val lootBoxOne: LootBox<Fedora> = LootBox(Fedora("평범한 중절모", 15))
val lootBoxTwo: LootBox<Coin> = LootBox(Coin(15))
}
Generic을 사용해서 서로 다른 타입의 인스턴스를 갖는 LootBox 인스턴스를 생성한다.
제네릭 함수
class LootBox<T>(item: T) {
var open = false
private var loot: T = item
fun fetch(): T? {
return loot.takeIf { open }
}
}
fun main(args: Array<String>) {
val lootBoxOne: LootBox<Fedora> = LootBox(Fedora("평범한 중절모", 15))
val lootBoxTwo: LootBox<Coin> = LootBox(Coin(15))
lootBoxOne.open = true
lootBoxOne.fetch()?.run {
println("$name 를 LootBox에서 꺼냈습니다.")
}
}
복합 제네릭 타입 매개변수
제네릭 함수나 타입의 매개변수에는 또 다른 제네릭 타입 매개변수를 사용할 수도 있다.
class LootBox<T>(item: T) {
var open = false
private var loot: T = item
fun fetch(): T? {
return loot.takeIf { open }
}
fun <R> fetch(lootModFunction: (T) -> R): R? {
return lootModFunction(loot).takeIf { open }
}
}
fun main(args: Array<String>) {
val lootBoxOne: LootBox<Fedora> = LootBox(Fedora("평범한 중절모", 15))
val lootBoxTwo: LootBox<Coin> = LootBox(Coin(15))
lootBoxOne.open = true
lootBoxOne.fetch()?.run {
println("$name 를 LootBox에서 꺼냈습니다.")
}
val coin = lootBoxOne.fetch {
Coin(it.value * 3)
}
coin?.let { println(it.value) }
}
제네릭 타입 제약
Loot의 서브클래스만 LootBox 클래스의 매개변수 타입으로 사용할 수 있다.
class LootBox<T: Loot>(item: T) {
var open = false
private var loot: T = item
fun fetch(): T? {
return loot.takeIf { open }
}
fun <R> fetch(lootModFunction: (T) -> R): R? {
return lootModFunction(loot).takeIf { open }
}
}
open class Loot(val value: Int)
class Fedora(val name: String, value: Int): Loot(value)
class Coin(value: Int): Loot(value)
in, out
class Barrel<T>(var item: T)
fun main(args: Array<String>) {
var fedoraBarrel: Barrel<Fedora> = Barrel(Fedora("평범한 중절모", 15))
var lootBarrel: Barrel<Loot> = Barrel(Coin(15))
// 에러 발생
lootBarrel = fedoraBarrel
}
제네릭 클래스의 인스턴스는 원시 타입과 제네릭 타입이 결합된 것이다.
즉 fedoraBarrel의 타입은 Barrel이 아닌 Barrel
이런 문제를 해결하는 방법이 in, out 키워드이다.
out 키워드 추가하기
class Barrel<out T>(val item: T)
기존 소스와 다른점
- item이 val로 바뀜
- out 키워드를 추가: 제네릭 타입 간의 슈퍼-서브 관계를 컴파일러가 고려해 준다.
reified
컴파일된 JVM 바이트코드에는 제네릭 타입 매개변수의 정보가 소거되기 때문에 타입 검사를 할 수 없다. reified 키워드와 inline 키워드를 함께 지정하면 타입 매개변수의 타입 검사를 런타임 시에 할 수 있다.