为什么可以从 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
。它只检查 sum
是 Number
,就是这样。没有执行任何其他操作。
您可以通过打印看到 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)
在下文中,我有一个带有类型参数 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
。它只检查 sum
是 Number
,就是这样。没有执行任何其他操作。
您可以通过打印看到 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)