반응형
Notice
Recent Posts
Recent Comments
Link
«   2024/09   »
1 2 3 4 5 6 7
8 9 10 11 12 13 14
15 16 17 18 19 20 21
22 23 24 25 26 27 28
29 30
Tags
more
Archives
Today
Total
관리 메뉴

안드로이드 개발자 노트

[이펙티브 코틀린] Item22. 일반적인 알고리즘을 구현할 때 제네릭을 사용하라 본문

Kotlin/이펙티브 코틀린

[이펙티브 코틀린] Item22. 일반적인 알고리즘을 구현할 때 제네릭을 사용하라

어리둥절범고래 2023. 11. 11. 19:40
반응형

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

타입 아규먼트를 사용하는(타입 파라미터를 갖는) 함수를 제네릭 함수라고 부릅니다.

예를 들어, slice 함수는 타입 파라미터 T를 갖습니다.

public fun <T> List<T>.slice(indices: IntRange): List<T> {
    if (indices.isEmpty()) return listOf()
    return this.subList(indices.start, indices.endInclusive + 1).toList()
}

 

타입 파라미터는 컴파일러에 타입과 관련된 정보를 제공하여 컴파일러가 타입을 정확하게 추측할 수 있게 해줍니다.

 

제네릭은 기본적으로 List<String> 또는 Set<User>처럼 구체적인 타입으로 컬렉션을 만들 수 있게 클래스와 인터페이스에 도입된 기능입니다.
이러한 타입 정보 덕분에 MutablSet<User>에 User를 추가할 수 있으며 MutablSet<User>에서 요소를 꺼내면, 그것이 User라는 것을 알 수 있습니다.
코틀린은 강력한 제네릭 기능을 갖고 있지만, 조금 복잡해서 이해하기 어렵습니다.
코틀린 제네릭의 중요한 특징들에 대해서 '아이템 24: 제네릭 타입과 variance 한정자를 활용하라'를 통해 살펴봅시다.

 

 

제네릭 제한

 

타입 파라미터의 중요한 기능 중 하나는 구체적인 타입의 서브타입만 사용하게 타입을 제한하는 것입니다.

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

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

 

타입 제한에 걸리므로, 내부에서 해당 타입이 제공하는 메서드를 사용할 수 있습니다.

예를 들어 T를 Iterable<Int>의 서브타입으로 제한하면, T 타입을 기반으로 반복 처리가 가능하고, 반복 처리 때 사용되는 객체가 Int라는 것을 알 수 있습니다.

또한 Comparable<T>로 제한하면, 해당 타입을 비교할 수 있다는 것을 알 수 있습니다.

많이 사용하는 제한으로는 Any가 있으며, 이는 nullable이 아닌 타입을 나타냅니다.

 

드물지만 다음과 같이 둘 이상의 제한을 걸 수도 있습니다.

fun <T : Animal> pet(animal: T) where T : GoodTempered {
  //...
}

// 또는

fun <T> pet(animal: T) where T : Animal, T : GoodTempered {
  //...
}

 

 

where 를 사용한 파라미터 타입 제한

 

제네릭은 파라미터에 타입을 쓸 수 있도록 유연함을 제공해줍니다.

하지만 때때로 너무 많은 유연성은 올바른 선택이 아닐 때가 있습니다.

여러 타입을 사용할 수 있지만 제약조건이 필요한 경우도 분명 있습니다.
아래 예제를 살펴보겠습니다.

fun <T> useAndClose(input: T) {
    input.close()  // ERROR: unresolved reference: close
}


위 코드는 컴파일 에러를 발생시킵니다.

왜냐하면 T 타입에 close() 라는 함수가 있다는 것이 보장되어 있지 않기 때문입니다.

하지만 우리는 코틀린에게 인터페이스를 통해서 close() 함수가 있는 타입만 들어올 수 있도록 제약을 걸 수 있습니다.

예를 들어보자면 AutoCloseable 인터페이스가 있습니다.
함수를 제약조건을 사용해서 다시 정의해보겠습니다.

fun <T: AutoCloseable> useAndClose(input: T) {
    input.close()  // OK
}

 

이제 useAndClose() 함수는 모든 타입이 아닌, AutoCloseable 인터페이스를 구현한 클래스만이 파라미터로 전달 가능하게 됐습니다.
이처럼 하나의 제약조건을 넣기 위해서 파라미터 타입 뒤에 콜론(:)을 넣은 후 제약조건을 정의하면 됩니다.

하지만 여러 개의 제약 조건을 넣을 땐 이런 방식으론 불가능하며, 이럴 때는 where 를 사용해야 합니다.
파라미터 타입이 AutoCloseable을 만족하는 것에 추가로 Appendable을 제약조건으로 더해보도록 하겠습니다.

fun <T> useAndClose(input: T) where T: AutoCloseable, T: Appendable {
    input.append("there")
    input.close()
}

 

위와 같이 메소드 정의 끝부분에 where 절을 쓰고 콤마(,)로 구분해서 제약 조건을 나열합니다.

나열한 제약 조건들은 and 조건으로 위 예제에서는 AutoCloseable과 Appendable 인터페이스를 모두 구현한 클래스여야 합니다.
그러면 이제 우리는 전달받은 파라미터의 close() 함수와 append() 함수를 사용할 수 있게 됩니다.

반응형