调用Iterable<Int>.sum()处理字符串列表时,为何报错无法转为Number而非Int?
先看这段存在问题的代码:
fun printSum(c: Collection<*>) { val intList = c as? List<Int> // 类型擦除导致无法检查List的元素类型 ?: throw IllegalArgumentException("List is expected") println(intList.sum()) }
当传入字符串列表printSum(listOf("a", "b", "c"))时,抛出的异常是:
Exception in thread "main" java.lang.ClassCastException: class java.lang.String cannot be cast to class java.lang.Number (java.lang.String and java.lang.Number are in module java.base of loader 'bootstrap')
明明我们预期的是无法转换为Int,为什么报错提示无法转换为Number?
原因解析:
类型擦除的影响:
c as? List<Int>在运行时只会检查c是否是List类型,不会验证元素的实际类型(因为泛型类型参数在JVM运行时会被擦除)。所以intList的静态类型是List<Int>,但实际运行时是List<String>。sum()函数的字节码实现:Kotlin标准库中
Iterable<Int>.sum()的实现,编译为JVM字节码时,会先将遍历到的元素转换为Number类型,再拆箱为int进行累加。这是因为JVM中所有数值包装类(包括Integer)都继承自Number,这种实现可以复用部分逻辑。
当代码尝试从实际是String的列表中取出元素,执行checkcast java.lang.Number指令时,String并非Number的子类,因此直接抛出无法转换为Number的ClassCastException——因为转换检查发生在Number这一层,还没到具体的Integer类型检查环节。
验证:
如果手动模拟sum()的遍历逻辑:
fun printSumManual(c: Collection<*>) { val intList = c as? List<Int> ?: throw IllegalArgumentException("List is expected") var sum = 0 for (element in intList) { sum += element // 这里的字节码会先尝试将element转成Number } println(sum) }
调用printSumManual(listOf("a", "b", "c"))会抛出和之前完全一致的异常,因为遍历赋值的字节码逻辑和sum()函数一致。
内容的提问来源于stack exchange,提问作者Kirill

