제네릭과 타입 불변성

제네릭

코틀린을 공부하던 중, 제네릭이라는 개념이 나왔다.

사실 이전에 자바를 공부할 때, 한번 다뤘던 개념이지만 별 생각없이 훑고 넘어갔었다. 지금 다시 보니 전혀 모르는 개념이 되었다. 따라서 이번 기회에 정리해보고자 한다.

우선 사전적 의미를 확인 해보자.

위키백과에서 제네릭을 검색하면 다음과 같은 내용이 나온다.

데이터 형식에 의존하지 않고, 하나의 값이 여러 다른 데이터 타입들을 가질 수 있는 기술에 중점을 두어 재사용성을 높일 수 있는 프로그래밍 방식...

난해하다.

제네릭은 우리가 코드를 재사용함에 있어서, 타입 안정성을 저하시키는 경우에 대한 방지책으로 나온 것이라고 한다..(예를 들어 파라미터로 값을 Any [자바에서는 Object]로 받는 경우 → 타입 안정성을 저하시킴)

따라서 제네릭을 사용하면 여러 데이터타입을 사용할 수 있는 코드를 작성할 수 있다.

사실 모든 프로그래밍은 글로 읽기만 하면 이해가 안된다. 예제를 통해 확인해 보자.

타입 불변성

우선 Fruit 클래스와 이를 각각 상속 받는 Banana, Orange 클래스를 만들어 보자

타입 불변성 예제

open class Fruit
class Banana : Fruit()
class Orange : Fruit()

fun main() {
    fun receiveFruits(fruits: Array<Fruit>) {
        println("Number of fruits: ${fruits.size}")
    }

    val bananas:List<Banana> =listOf()
    receiveFruits(bananas)
}

위 코드를 타이핑 해보면 컴파일 에러를 확인할 수 있다.


함수의 파라미터가 Array 타입을 인자로 받기를 원하는데 Array 타입을 파라미터에 넣어주었기 때문에 Type mismatch 컴파일 에러를 확인 할 수 있다.

사실 제네릭에 대한 개념이 있기 전에는 당연히 가능할 줄 알았다. 상속이라는 개념 자체가 자식 클래스는 부모 클래스에 대한 정보가 있기 때문에, Array로 전달 하여도 banana는 fruit에 대한 정보가 있을 것이라 예상하였다.

하지만 실상은 그렇지가 않다.

이는 코틀린에서 제네릭의 타입 불변성 이라는 개념 때문에 발생하는 상황이다.

그렇다면 이 타입 불변성이 어떤 이유로 필요한 것인지 예제 코드로 확인해보자.

타입 불변성 필요 이유 예제 코드

open class Fruit
class Banana : Fruit()
class Orange : Fruit()

fun main() {
    val bananas: Array<Banana> = arrayOf(Banana())
    receiveFruits(bananas)
}

fun receiveFruits(fruits: Array<Fruit>) {
   fruits[0] = Orange() // 바나나 배열에 오렌지를 넣으면 문제가 됨
}

Array 객체를 Array을 인자로 받는 파라미터로 전달 할 수 있다면,Banana들이 담겨 있는 fruits에 Orange 객체를 담게되면서 문제가 발생한다. 왜냐하면 Banana와 Orange가 서로 같은 타입으로 취급 될 수 없기 때문이다.

즉 코틀린에서는 Banana 가 Fruit을 상속 받았다고 해도 Array를 Array 으로 전달을 불가하게 하여 제네릭을 타입 안정적으로 만들었다. 하지만 다음 코드를 살펴보자

open class Fruit
class Banana : Fruit()
class Orange : Fruit()

fun main() {
    val bananas: List<Banana> = listOf(Banana(),Banana())
    receiveFruits(bananas)
}

fun receiveFruits(fruits: List<Fruit>) {
    println(fruits.size) // 2
}

위 코드는 정상적으로 작동한다. 단지 Array를 list로 바꿔줬을 뿐인데도 말이다.

그 이유를 알기위해서는 우선 코틀린에서의 Array와 List에 대해 알아야한다.

Array는 코틀린에서 가변(mutable)로 취급된다. 이와 반대로 List는 불변로 취급된다. 이를 확인해보기 위해 Array와 List 문서를 각각 확인해보자.

Array - 문서

List - 문서


List의 선언부 확인해보면 List 라고 선언된 것을 볼 수 있다. 이와 반대로 Array는 Array로 선언되어있다. 이 둘의 차이점은 out 이라는 키워드의 유무다.

에 대해서는 이 뒷 내용에 다루겠다.

우선 List는 List 이기 때문에 코틀린 컴파일러가 공변성을 허용하여 위와 같은 코드를 허용한다.

공변성에 대해서는 후에 다루겠다.

좋은 웹페이지 즐겨찾기