为什么可以从 Double 转换为 <T : Number>,但不能从 Double 转换为 Int?

Mar*_*ger 5 generics casting classcastexception kotlin

在下面,我有一个fun <T : Number> sum(list : List<T>) : T带有类型参数的泛型函数T : Number

在函数中,我将列表的数字总结为 asum : Double并将总和最后转换为return sum as T

例如,如果Int传递了一个列表,我也会返回一个Int- 并且这有效。

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
Run Code Online (Sandbox Code Playgroud)

然而,以下内容不起作用,我想知道为什么上面的通用函数有效,但直接将 a 转换Double为 anInt却不起作用。

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)
}
Run Code Online (Sandbox Code Playgroud)

我不得不承认,我预计这两种情况都会失败,但令人惊讶的是,通用函数有效,但我不知道为什么。

所以问题是:为什么泛型函数可以工作,并且在从 Double 转换为 Int 时不会抛出 ClassCastException?

Swe*_*per 7

请注意,在第一个“有效”的代码片段中,您实际上并未将结果转换为Int. 如果您使用 IntelliJ,它应该将转换标记为“未经检查的转换”。这意味着在运行时,不会检查是否sum实际上可以转换为 type T。它只检查是否sum是 a Number,仅此而已。不执行任何其他操作。

通过打印可以看到返回值仍然是 a Double,而不是 an Int

println(sum(listOf<Int>(1,2,3)) is Int) // false
println(sum(listOf<Int>(1,2,3)) is Double) // true
Run Code Online (Sandbox Code Playgroud)

正如其他答案所解释的,这是因为类型擦除。

6您仍然看到但没有看到的原因6.0有点复杂。Kotlin 编译器认为sum此处的调用应返回 an Int(此时类型尚未被删除),因此它找到println采用 an 的重载Int,该重载内联到 JavaSystem.out.prinln(int)方法。要调用此方法,编译器必须生成将返回的类型擦除的代码转换为Number,因此它调用.sumintNumber.intValue

因此,这就是生成的内容:

  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
Run Code Online (Sandbox Code Playgroud)

如果您强制编译器调用pritnln(Any),那么它将打印6.0

val any: Any = sum(listOf<Int>(1,2,3))
println(any)
Run Code Online (Sandbox Code Playgroud)