관리 메뉴

나만을 위한 블로그

[이펙티브 코틀린] 아이템 22. 일반적인 알고리즘 구현 시 제네릭을 써라 본문

책/Effective Kotlin

[이펙티브 코틀린] 아이템 22. 일반적인 알고리즘 구현 시 제네릭을 써라

참깨빵위에참깨빵_ 2022. 9. 12. 21:52
728x90
반응형

아규먼트로 함수에 값을 전달할 수 있는 것처럼 타입 아규먼트를 사용하면 함수에 타입을 전달할 수 있다.

타입 아규먼트를 사용하는 함수(즉 타입 파라미터를 갖는 함수)를 제네릭 함수라고 부른다. 대표적인 예로는 stdlib의 filter()가 있다. filter()는 타입 파라미터 T를 갖는다.

 

inline fun <T> Iterable<T>.filter(
    predicate: (T) -> Boolean
): List<T> {
    val destination = ArrayList<T>()
    for (element in this) {
        if (predicate(element)) {
            destination.add(element)
        }
    }
    
    return destination
}

 

filter()의 람다 표현식 내부에서 컴파일러가 아규먼트가 컬렉션의 요소와 같은 타입이라는 걸 알 수 있으므로 잘못 처리하는 걸 막을 수 있다. IDE도 이를 기반으로 여러 유용한 제안을 해 준다.

 

 

제네릭은 기본적으로 List<String> 또는 Set<User>처럼 구체적인 타입으로 컬렉션을 만들 수 있게 클래스, 인터페이스에 도입된 기능이다. 컴파일 과정에서 최종적으로 이런 타입 정보는 사라지지만 개발 중에는 특정 타입을 사용하게 강제할 수 있다. 이런 타입 정보 덕분에 MutableList<Int>에 안전하게 Int를 추가할 수 있다.

또한 Set<User>에서 요소를 꺼내면 그것이 User라는 걸 알 수 있다. 이런 기능은 정적 타입 프로그래밍 언어에선 굉장히 유용하게 활용된다.

코틀린은 강력한 제네릭 기능을 갖고 있지만 조금 복잡해서 이해하기 어렵다. 필자의 경험에 의하면 많은 코틀린 개발자가 variance 한정자를 어떤 형태로 사용하는지 잘 몰랐다.

 

제네릭 제한

 

타입 파라미터의 중요한 기능 중 하나는 구체적인 타입의 서브타입만 사용하게 타입을 제한하는 것이다. 아래 코드는 콜론 뒤에 슈퍼타입을 설정해 제한을 걸었다.

 

fun <T: Comparable<T>> Iterable<T>.sorted(): List<T> {
    /* ... */
}

fun <T, C : MutableCollection<in T>>
Iterable<T>.toCollection(destination: C): C {
    /* ... */
}

class ListAdapter<T: ItemAdapter> (/* ... */) { /*...*/ }

 

타입에 제한이 걸리므로 내부에서 해당 타입이 제공하는 메서드를 사용할 수 있다. 예를 들어 T를 Iterable<Int>의 서브타입으로 제한하면 T 타입을 기반으로 반복 처리가 가능하고 반복 처리 시 사용하는 객체가 Int라는 걸 알 수 있다. 또한 Comparable<T>로 제한하면 해당 타입을 비교할 수 있다는 걸 알 수 있다. 많이 사용하는 제한으로는 Any가 있다. 이는 nullable이 아닌 타입을 나타낸다.

 

inline fun <T, R: Any> Iterable<T>.mapNotNull(
    transform: (T) -> R?
): List<R> {
    return mapNotNullTo(ArrayList<R>(), transform)
}

 

 

반응형
Comments