In the following, I have a generic function fun <T : Number> sum(list : List<T>) : T
with a type parameter T : Number
.
In the function, I sum up the list's numbers as a sum : Double
and cast the sum in the end with return sum as T
.
For example, if a list of Int
is passed, I also get back an Int
- and this works.
fun <T : Number> sum(list : List<T>) : T {
var sum = 0.0
for(x in list)
sum += x.toDouble()
return sum as T
}
fun main() { println(sum(listOf(1,2,3))) } // prints 6
Yet, the following does not work, and I am wondering why the generic functions above works but directly casting a Double
to an Int
does not.
fun main() {
val d : Double = 6.0
val i = d as Int // java.lang.ClassCastException: java.lang.Double cannot be cast to java.lang.Integer
println(i)
}
I have to admit that I expected both cases to fail, but surprisingly, the generic function works and I don't know why.
So the question is: Why does the generic function work and does not throw a ClassCastException when casting from Double to Int?
Notice that in the first code snippet, which "works", you are not actually casting the result to Int
. If you are using IntelliJ, it should have marked the cast as an "unchecked cast". This means that at runtime, it is not checked whether sum
can actually be converted to type T
. It only checks that sum
is a Number
, and that's it. Nothing else is performed.
You can see that the returned value is still a Double
, not an Int
by printing:
println(sum(listOf<Int>(1,2,3)) is Int) // false
println(sum(listOf<Int>(1,2,3)) is Double) // true
And as other answers explain, this is because of type erasure.
The reason why you still see 6
, but not 6.0
is bit more involved. The Kotlin compiler sees that the sum
call here should return an Int
(at this point the types have not been erased yet), so it finds the println
overload that takes an Int
, which inlines to the Java System.out.prinln(int)
method. To call this method, the compiler must generate code that converts the type-erased Number
that sum
returns to an int
, so it calls Number.intValue
.
Therefore, this is what's generated:
33: invokestatic #69 // Method sum:(Ljava/util/List;)Ljava/lang/Number;
36: invokevirtual #73 // Method java/lang/Number.intValue:()I
39: invokevirtual #79 // Method java/io/PrintStream.println:(I)V
If you had forced the compiler to call pritnln(Any)
, then it would print 6.0
:
val any: Any = sum(listOf<Int>(1,2,3))
println(any)