
안녕하세요.
오늘은 Generic에 대해서 알아보겠습니다.
제네릭?
제네릭은 클래스나 함수에서 타입을 파라미터화 할 수 있는 기능입니다.
코드 작성 시점에 구체적인 타입을 지정하지 않고, 타입을 변수처럼 처리할 수 있어 여러 타입을 다룰 수 있게 합니다.
제네릭을 사용하면 2가지 장점을 얻을 수 있습니다.
1) 타입 안정성
컴파일 타임에 타입 오류를 검출해 타입 안정성을 강화합니다.
제네릭을 안 쓰게 되면, 컴파일 시점에 아래 오류를 못 잡고 런타임 에러가 발생합니다.
fun addToList(list: List<Any>, item: Any): List<Any> {
val mutableList = list.toMutableList()
mutableList.add(item)
return mutableList
}
fun main() {
val list = listOf(1, 2, 3)
val result = addToList(list, "Hello")
println(result)
val firstElement: Int = result[3] as Int // ClassCastException 발생
}

제네릭을 사용하면 컴파일 에러를 내서 오류를 잡을 수 있습니다.
fun <T> addToList(list: List<T>, item: T): List<T> {
val mutableList = list.toMutableList()
mutableList.add(item)
return mutableList
}
fun main() {
val list = listOf(1, 2, 3)
val result = addToList<Int>(list, "Hello")
println(result)
}

2) 코드의 재사용성
타입별로 함수를 생성하지 않아도 됩니다.
fun <T> compareValues(a: T, b: T): Boolean {
return a == b
}
fun main() {
println(compareValues<Int>(10, 10)) // 출력: true
println(compareValues<String>("Hello", "World")) // 출력: false
// println(compareValues<Int>(10, "Hello")) // 컴파일 오류: 타입 불일치
}
제네릭 기본 문법
1) 제네릭 클래스
제네릭 클래스는 클래스 정의에서 타입 파라미터를 사용하여 특정 타입에 제한되지 않고 다양한 타입을 처리할 수 있게 합니다.
class Box<T>(var value: T)
fun main() {
val intBox = Box(10) // Box<Int>로 자동 추론됨
val stringBox = Box("Hello") // Box<String>으로 자동 추론됨
println(intBox.value) // 출력: 10
println(stringBox.value) // 출력: Hello
}
2) 제네릭 함수
제네릭 함수는 함수 정의에서 타입 파라미터를 사용하여 함수가 여러 타입을 처리할 수 있도록 합니다.
두 개 이상의 제네릭 타입을 지정할 수도 있습니다.
fun <T, U> processData(data: T, value: U) {
println("Data: $data, Value: $value")
}
fun main() {
processData("Kotlin", 100) // 출력: Data: Kotlin, Value: 100
processData(42, 3.14) // 출력: Data: 42, Value: 3.14
processData(listOf(1, 2, 3), "List") // 출력: Data: [1, 2, 3], Value: List
}
타입 제약 (Type Constraints)
타입 파라미터에 특정 제약을 설정하여, 지정된 타입만 허용할 수 있습니다.
2가지 방법으로 타입 제약을 구현할 수 있습니다.
1) 상위 타입 제한 ( : )
T를 특정 타입으로 제한합니다.
fun <T : Number> add(a: T, b: T): Double {
return a.toDouble() + b.toDouble()
}
fun main() {
println(add(1, 2)) // 3.0
// println(add("Hello", "World")) // 컴파일 에러
}

2) 다중 제약 (where)
where 키워드를 사용해 타입 매개변수에 여러 제약을 추가할 수 있습니다.
interface Printable {
fun print()
}
open class Document(val name: String)
class Report(name: String) : Document(name), Printable {
override fun print() {
println("Printing report: $name")
}
}
// T는 Document를 상속받아야 하고, Printable 인터페이스를 구현해야 한다.
fun <T> printDocument(doc: T)
where T : Document, T : Printable {
doc.print()
}
fun main() {
val report = Report("Annual Report")
printDocument(report) // 출력: Printing report: Annual Report
}
Document 상속을 받지 않는 클래스를 printDocument()에 넣으면 컴파일 에러가 발생합니다.
class Button() : Printable {
override fun print() {
println("Button Clicked!")
}
}
// T는 Document를 상속받아야 하고, Printable 인터페이스를 구현해야 한다.
fun <T> printDocument(doc: T)
where T : Document, T : Printable {
doc.print()
}
fun main() {
val button = Button()
printDocument(button)
}

'Kotlin' 카테고리의 다른 글
[Kotlin] Generic (3) - 그 외 (2) | 2024.12.19 |
---|---|
[Kotlin] Generic (2) - 변성 (0) | 2024.12.10 |
[Kotlin] 위임 (Delegation) (2) | 2024.11.26 |
[Kotlin] DSL (Domain Specific Language) (0) | 2024.11.05 |
[Kotlin] SAM & invoke (1) | 2024.10.30 |