【问题标题】:Kotlin - Type Erasure and CastingKotlin - 类型擦除和铸造
【发布时间】:2021-02-13 12:45:56
【问题描述】:

我试图了解如何从代码中抛出 ClassCastException,据我所知,被转换为的类型已被删除。

示例

class Foo<T : Any>(val key: String, val value: T) {
    override fun toString() = "$key: $value"
}

fun <T : Foo<*>> findByKey(items: Iterable<Foo<*>>, key: String): List<T> {
    return items.filter { it.key == key } as List<T>
}

fun main() {
    val items = listOf(
        Foo("a", "abc"), 
        Foo("a", 12345), 
        Foo("a", false)
    )
    
    val filtered = findByKey<Foo<String>>(items, "a")
    println(filtered)
    
    val values = filtered.map { it.value }
    println(values)
}

输出

[a: abc, b: 12345, c: 假]

线程“main”java.lang.ClassCastException 中的异常:java.lang.Integer 无法转换为 java.lang.String

这里有几个疼的拇指:

  • findByKey 中键入T 不是reified,因此被删除。
  • findByKey 接受 Iterable&lt;Foo&lt;*&gt; 而不是 Iterable&lt;T&gt;(故意)。
  • List&lt;T&gt; 存在未经检查的强制转换。

我觉得有趣的是:

  • 调用findByKey&lt;Foo&lt;String&gt;&gt;(items, "a") 不会抛出ClassCastException
  • 在 JVM 中,我希望此调用为 findByKey(items, "a"),因为 &lt;Foo&lt;String&gt;&gt; 已被删除。
  • 调用filtered.map { it.value } 会抛出ClassCastException

我不是在寻找如何让这段代码正常工作的解决方案;我已经知道了。相反,我很想知道为什么调用map 会为我认为会被删除的类型抛出ClassCastException

【问题讨论】:

标签: kotlin


【解决方案1】:

该类型在编译时尚未擦除。编译器知道您已转换为 List&lt;Foo&gt;,并且必须将各个项目转换为 Foo 才能在 map 中获取它们的 value 属性。在运行时,源类型未知,但调用其value 属性所需的目标类型已知的。所以这个演员表在编译时是硬编码的,在任何擦除之前。

异常消息告诉您产生转换错误的单个项目的源类型。您的 List 的编码类型未知,但可以检查单个对象以确定它们的类型。

【讨论】:

  • 是否可以从findByKey函数内部获取String的类型信息?
  • 对不起,我不明白你的问题。但是您可以将items的参数类型更改为Iterable&lt;T&gt;,然后您根本不必将其转换为List&lt;T&gt;
  • Iterable&lt;Foo&lt;*&gt;&gt; 是有原因的,因为它代表 Kotlin 之外的数据源。我要问的是,如果编译器在某个时候知道结果已经被强制转换,以及它被转换成什么,你能从任何地方获取类型信息(无需具体化类型)吗?
  • 您可以使用::class 检索特定对象的类,并且可以检查其类型是否与is 匹配。您在运行时无法检索的是泛型类型。因此,您可以遍历 List&lt;*&gt;List&lt;Any&gt; 并使用 is 检查每个单独的项目以安全地投射它们。但是你永远也无法知道整个 List 的泛型是什么。
【解决方案2】:

如果你反编译你的代码,你会看到map函数被翻译成下面的代码:

Iterable $this$map$iv = (Iterable)filtered;
Collection destination$iv$iv = (Collection)(new ArrayList(CollectionsKt.collectionSizeOrDefault($this$map$iv, 10)));
Iterator var8 = $this$map$iv.iterator();
while(var8.hasNext()) {
   Object item$iv$iv = var8.next();
   Foo it = (Foo)item$iv$iv;
   String var13 = (String)it.getValue();
   destination$iv$iv.add(var13);
}
List values = (List)destination$iv$iv;

这行String var13 = (String)it.getValue(); 抛出了ClassCastException

【讨论】:

  • 那么实际上String并没有被删除?有没有办法从 Kotlin 获取这些信息?
猜你喜欢
  • 1970-01-01
  • 2013-02-17
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2014-12-26
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多