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

Why is a cast from Double to <T : Number> possible, but not from Double to Int?

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

在函数中,我将列表中的数字相加为 sum : 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

然而,以下不起作用,我想知道为什么上面的通用函数可以工作,但直接将 Double 转换为 Int 却不行。

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)
}

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

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

这是因为在运行时保留类型信息的 type erasure. In your case the generic info is only available during compile. At runtime the sum as T does noting, because it's not clear what T is. For example it's not possible to print out the type of T. It's an unchecked cast. You could also change the T type from Number to String, which makes no sense - but it would compile. So the function will not trow the ClassCastException because actual it does no cast. If you change the sum function to a refined type,它会进行转换并抛出错误:

inline fun <reified  T : Number> sum(list: List<T>): T {
    var sum = 0.0
    for (x in list)
        sum += x.toDouble()
    return sum as T // java.lang.ClassCastException
}

它执行强制转换,但强制转换为您的通用类型的上限,即 Number

您可以在代码的反编译版本中看到这种情况:

   public static final Number sum(@NotNull List list) {
      Intrinsics.checkNotNullParameter(list, "list");
      double sum = 0.0D;

      Number x;
      for(Iterator var4 = list.iterator(); var4.hasNext(); sum += x.doubleValue()) {
         x = (Number)var4.next();
      }

      return (Number)sum;
   }

如果您查看该代码,编译器还会告诉您强制转换为 T 是多余的,因为这已经得到保证。

等同于:

fun main() {        
    val d : Double = 6.0
    val i = d as Number 
    println(i)
}

这也不会导致 ClassCast 异常。

请注意,在“有效”的第一个代码片段中,您实际上并未将结果转换为 Int。如果您使用的是 IntelliJ,它应该将演员表标记为“未经检查的演员表”。这意味着在运行时,不会检查 sum 是否真的可以转换为类型 T。它只检查 sumNumber,就是这样。没有执行任何其他操作。

您可以通过打印看到 returned 值仍然是 Double,而不是 Int

println(sum(listOf<Int>(1,2,3)) is Int) // false
println(sum(listOf<Int>(1,2,3)) is Double) // true

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

您仍然看到 6 而看不到 6.0 的原因有点复杂。 Kotlin 编译器看到这里的 sum 调用应该 return 一个 Int (此时类型还没有被擦除),所以它发现 println 重载需要一个 Int,内联到 Java System.out.prinln(int) 方法。要调用此方法,编译器必须生成代码,将 sum return 的类型擦除 Number 转换为 int,因此它调用 Number.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

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

val any: Any = sum(listOf<Int>(1,2,3))
println(any)