본문 바로가기

Study/Kotlin Study

[Kotlin] 제네릭스

+ 항공대학교 김철기 교수님의 객체 지향 프로그래밍 과목 내용를 정리한 글입니다.

제네릭과 파라미터

제네릭이란?

=> 클래스 내부에서 사용하는 특정 대상의 타입을 파라미터화 한 것

 

ex) ArrayList<T> : T 타입을 가지는 원소의 ArrayList

ex) HashMap<K, V> : K타입의 키와 V타입의 값을 가지는 Entry의 HashMap

 

+ T, K V 를 타입 파라미터라고 한다.

 

제네릭을 사용하여 참조 변수 및 객체를 정의할 수 있다.

- 타입 파라미터에 대한 실제 타입을 지정해야한다.

- 타입 추론이 가능할 경우 타입 인자를 생략할 수 있다.

 

< 제네릭 사용 예시 코드 1 >

fun main() {
    val list = ArrayList<Int>()

    list.add(5)
    list.add(6)
    // list.add("Hello")는 에러 발생

    list.forEachIndexed { idx, v ->
        println("$idx: $v")
    }

    val list2 = ArrayList<String>()

    list2.add("Hello")
    list2.add("World")

    list2.forEachIndexed { idx, v ->
        println("$idx: $v")
    }
}

 

< 제네릭 사용 예시 코드 2 >

fun main() {
    val map = HashMap<String, Int>()

    map["I"] = 1
    map["V"] = 5
    map["X"] = 10
    map["L"] = 50

    map.forEach {k, v ->
        println("$k -> $v")
    }
}

 

< 타입 추론 예시 코드 > 

fun main() {
    val map: HashMap<String, Int> = HashMap()
    val arr = arrayOf("abc", "def")
}

제네릭 정의

사용자가 제네릭을 정의할 수 있다.

class TreeNode<T>(val data: T) {
    var parent: TreeNode<T>? = null
        private set
    
    // 자식들을 담을 리스트
    private val _children = arrayListOf<TreeNode<T>>()
    val children: List<TreeNode<T>> get() = _children

    // 자식 문자열을 TreeNode 객체로 생성 후 _children 리스트에 추가 + 자식 객체의 부모를 자신으로 변경
    fun addChild(data: T) = TreeNode<T>(data).also {
        _children += it
        it.parent = this
    }

    override fun toString() = _children.joinToString(prefix = "$data {", postfix = "}")
}

fun main() {
    // <String>을 적지 않아도 타입 추론 가능
    val root = TreeNode("Hello").apply {
        addChild("World")
        addChild("!!")
    }
    println(root) // Hello {World {}, !! {}}
}

제네릭의 상속

타입 파라미터를 특정하여 일반 클래스 형태로 상속이 가능하다.

 

타입 파라미터를 상위 타입의 타입 파라미터로 넘길 수 있다.

 

타입 파라미터를 가지는 함수도 만들 수 있다.

open class DataHolder<T>(val data:T)

class TreeNode<T>(data: T) : DataHolder<T>(data){
    var parent: TreeNode<T>? = null
        private set

    // 자식들을 담을 리스트
    private val _children = arrayListOf<TreeNode<T>>()
    val children: List<TreeNode<T>> get() = _children

    // 자식 문자열을 TreeNode 객체로 생성 후 _children 리스트에 추가 + 자식 객체의 부모를 자신으로 변경
    fun addChild(data: T) = TreeNode<T>(data).also {
        _children += it
        it.parent = this
    }

    override fun toString() = _children.joinToString(prefix = "$data {", postfix = "}")
}

// 제네릭 확장 함수 선언
fun<T> TreeNode<T>.addChildren(vararg data: T) { // vararg: 같은 타입의 데이터 여러 개를 입력 받을 수 있다.
    data.forEach {
        addChild(it)
    }
}

fun main() {
    // <String>을 적지 않아도 타입 추론 가능
    val root = TreeNode("Hello").apply {
        addChildren("World", "!!")
    }
    println(root) // Hello {World {}, !! {}}
}

타입 파라미터의 바운드

타입 파라미터가 특정 class나 interface를 상속한 경우로만 제네릭을 한정하는 것도 가능하다.

 

open class DataHolder<T>(val data:T)

class TreeNode<T>(data: T) : DataHolder<T>(data){
    var parent: TreeNode<T>? = null
        private set

    // 자식들을 담을 리스트
    private val _children = arrayListOf<TreeNode<T>>()
    val children: List<TreeNode<T>> get() = _children

    // 자식 문자열을 TreeNode 객체로 생성 후 _children 리스트에 추가 + 자식 객체의 부모를 자신으로 변경
    fun addChild(data: T) = TreeNode<T>(data).also {
        _children += it
        it.parent = this
    }

    override fun toString() = _children.joinToString(prefix = "$data {", postfix = "}")
}

// 제네릭 확장 함수 선언
fun<T> TreeNode<T>.addChildren(vararg data: T) { // vararg: 같은 타입의 데이터 여러 개를 입력 받을 수 있다.
    data.forEach {
        addChild(it)
    }
}

fun<T: Number> TreeNode<T>.averageChildren(): Double {
    var count = 0
    var sum = 0.0

    children.forEach{
        sum += it.data.toDouble()
        count++
    }
    return sum / count
}

fun main() {
    val root = TreeNode(1).apply {
        addChildren(2, 3)
    }
    println(root.averageChildren()) // 2.5
}

 

< 에러 발생 > 

fun main() {
    val root = TreeNode("Hello").apply {
        addChildren("World", "!!")
    }
    println(root.averageChildren()) // Number를 상속하지 않는 String형은 불가하므로 에러 발생
}

'Study > Kotlin Study' 카테고리의 다른 글

[Kotlin] 파일과 I/O 스트림  (2) 2023.11.27
[Kotlin] 컬렉션 유틸리티  (1) 2023.11.21
[Kotlin] 컬렉션 타입  (0) 2023.11.20
[Kotlin] 봉인된 클래스  (1) 2023.11.14
[Kotlin] 부호 없는 정수  (0) 2023.11.14