이 문제를 ‘유니버설 타입(universal type)’으로 해결할 수도 있을 것 같다. 유니버설 타입은 모든 타입의 부모 타입이다. 코틀린에서는 Any가 유니버설 타입이다. Any라는 이름의 뜻대로 Any 타입은 모든 타입의 인자를 허용한다. 어떤 함수에 여러 타입의 값을 넘겨야 하는데, 각 타입 사이에 공통점이 없다면 Any가 문제를 해결해준다.
언뜻 생각해보면 GenericHolder.kt에서 T 대신 Any를 써도 될 것 같다.
IntroGenerics/AnyInstead.kt
package introgenerics
import atomictest.eq
class AnyHolder(private val value: Any) {
fun getValue(): Any = value
}
class Dog {
fun bark() = "Ruff!"
}
fun main() {
val holder = AnyHolder(Dog())
val any = holder.getValue()
// 컴파일되지 않음
// any.bark()
val genericHolder = GenericHolder(Dog())
val dog = genericHolder.getValue()
dog.bark() eq "Ruff!"
}
간단한 경우에는 Any가 작동하지만, 구체적인 타입이 필요해지면(예를 들어 Dog의 bark()를 호출) 제대로 작동하지 않는다. 객체를 Any 타입으로 대입하면서 객체 타입이 Dog이라는 사실을 더 이상 추적할 수 없기 때문이다. Dog을 Any로 전달하기 때문에 결과는 그냥 Any이고, Any는 bark()를 제공하지 않는다.
여기서 제네릭스를 사용하면 실제 컬렉션에 Dog을 담고 있다는 정보를 유지할 수 있다. 이 말은 getValue()가 돌려주는 값에 대해 bark()를 적용할 수 있다는 뜻이다.