이펙티브 코틀린 item 22: 일반적인 알고리즘 구현 시 제네릭을 써라

woga·2023년 6월 25일
0

코틀린 공부

목록 보기
25/54
post-thumbnail

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

타입 아규먼트를 사용하는 함수(즉, 타입 파라미터를 갖는 함수)를 제네릭 함수라고 부른다. 대표적인 예로는 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 또는 Set처럼 구체적인 타입으로 컬렉션을 만들 수 있게 클래스, 인터페이스에 도입된 기능이다. 컴파일 과정에서 최종적으로 이런 타입 정보는 사라지지만 개발 중에는 특정 타입을 사용하게 강제할 수 있다. 이런 타입 정보 덕분에 MutableList에 안전하게 Int를 추가할 수 있다.

또한 Set에서 요소를 꺼내면 그것이 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의 서브타입으로 제한하면 T 타입을 기반으로 반복 처리가 가능하고 반복 처리 시 사용하는 객체가 Int라는 걸 알 수 있다. 또한 Comparable로 제한하면 해당 타입을 비교할 수 있다는 걸 알 수 있다. 많이 사용하는 제한으로는 Any가 있다. 이는 nullable이 아닌 타입을 나타낸다.

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

0개의 댓글