다양한 타입에서 사용 가능한 코드를 작성할 수 있다.
다음 코드는 컴파일 에러가 발생합니다. Double이 Number를 상속한다고 해서 List<Double>이 List<Number>를 상속한다고 말할 수 없기 때문입니다.
List<Double> doubles = Arrays.asList(1.1, 2.2, 3.3);
List<Number> numbers = doubles; // compile error
클래스의 상속관계가 Generics에서는 상속관계로 유지되지 않는 것을 Invariance라고 합니다.
List<Double>이 List<Number>를 상속하지 않는 이유는 Generics는 컴파일 단계에서 Generics의 타입이 지워지기 때문입니다. JVM은 Runtime에 List 객체만 알고 있고, Generics의 타입이 Double인지, Number인지 알지 못합니다.
클래스의 상속관계가 Generics에서도 상속관계가 유지되는 것을 말합니다.
List<Double> doubles = Arrays.asList(1.1, 2.2, 3.3);
List<Number> numbers = doubles; // ok
하지만, Covariance를 이용하여 객체를 할당하면, 객체를 read할 수 있지만 write는 어렵습니다.
get()은 문제없지만 add()는 컴파일에러가 발생합니다
Number number = numbers.get(0);
System.out.println(number);
numbers.add(1.1); // compile error
get()으로 리턴된 객체는 Number를 상속하는 Long, Integer, Dobule 중에 하나이고, Number로 할당받을 수 있습니다.
하지만, add()는 조금 다릅니다. 다음과 같이 List의 타입이 정해져있지 않아서 add()로 특정 클래스의 객체를 추가할 수 없습니다.
// compile error, List<>의 타입이 Double인지 알 수 없음
numbers.add(1.1);
// compile error, List<>의 타입이 Integer인지 알 수 없음
numbers.add(1);
// compile error, List<>의 타입이 Long인지 알 수 없음
numbers.add(1L);
클래스의 상속관계가 Generics에서는 반대인 것을 것을 의미합니다.
Reified 키워드는 Generics로 inline function에서 사용되며, Runtime에 타입 정보를 알고 싶을 때 사용합니다.
아래 코드는 컴파일 에러가 발생합니다. Runtime에 타입 정보가 지워지기 때문입니다.
fun <T> printGenerics(value: T) {
when (value::class) { // compile error!
String::class.java -> {
println("String : $value")
}
Int::class.java -> {
println("Integer : $value")
}
}
}
이럴 때 타입 정보가 있는 객체를 인자로 전달하면 문제를 해결할 수 있습니다.
아래와 같이 Class<T>로 인자를 전달하였고 이것으로 타입을 알 수 있습니다.
fun <T> printGenerics(value: T, classType: Class<T>) {
when (classType) {
String::class.java -> {
println("String : $value")
}
Int::class.java -> {
println("Int : $value")
}
}
}
printGenerics("print generics function", String::class.java)
printGenerics(1000, Int::class.java)
reified 키워드를 사용했기 때문에 T::class처럼 클래스의 타입 정보를 얻을 수 있습니다. 그래서 타입 정보를 인자로 넘기지 않아도 Runtime에 타입 정보를 알 수 있습니다.
inline fun <reified T> printGenerics(value: T) {
when (T::class) {
String::class -> {
println("String : $value")
}
Int::class -> {
println("Int : $value")
}
}
}
printGenerics1("print generics function")
printGenerics1(1000)
타입에 따라 리턴되는 타입이 다르도록 구현되었습니다.
inline fun<reified T> getMessage(number: Int): T {
return when (T::class) {
String::class -> "The number is : $number" as T
Int::class -> number as T
else -> "Not string, Not Integer" as T
}
}
위의 함수는 아래와 같이 사용할 수 있습니다. 리턴되는 타입에 따라서 T가 결정되며 T에 따라서 리턴되는 객체가 달라집니다.
val result : Int = getMessage(10)
println("result: $result")
val resultString : String = getMessage(100)
println("result: $resultString")
https://codechacha.com/ko/java-covariance-and-contravariance/
https://codechacha.com/ko/kotlin-reified-keyword/#reified-%ED%82%A4%EC%9B%8C%EB%93%9C%EB%A5%BC-%EC%82%AC%EC%9A%A9%ED%95%98%EC%97%AC-%ED%83%80%EC%9E%85-%EC%A0%95%EB%B3%B4-%EC%96%BB%EA%B8%B0
https://readystory.tistory.com/201