본문 바로가기

Kotlin

[Kotlin] Generic (1) - 제네릭?

powered by pixabay

 

안녕하세요.

오늘은 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' 카테고리의 다른 글