【问题标题】:Kotlin - Generic function storing incorrect type in map successfullyKotlin - 在地图中成功存储错误类型的通用函数
【发布时间】:2019-04-26 00:12:47
【问题描述】:

考虑以下代码:

val myarray = arrayListOf("hello", "world")
println(myarray)
println(myarray::class.java.name)

val arrayString = mapper.writeValueAsString(myarray)
println(arrayString)
println(arrayString::class.java.name)

val myMap = HashMap<String, String?>()
myMap["key"] = JsonPath.read<String>(arrayString, "$")

println(myMap["key"])
println(myMap["key"]!!::class.java.name)

此代码创建一个包含值“hello”和“world”的 ArrayList,将列表转换为 json 字符串,读取 json 的根元素(这是一个告诉返回字符串的通用函数)并将其存储在哈希映射

此代码编译并运行没有错误,并产生以下输出:

[hello, world]
java.util.ArrayList
["hello", "world"]
java.lang.String
["hello","world"]
net.minidev.json.JSONArray

myMap["key"] 如何返回JSONArray? (回想一下,myMap 被声明为&lt;String, String?&gt;

Kotlin 在编译时检查了它的类型,但是因为泛型函数 JsonPath.read 被告知返回 String,所以编译正常。

JsonPath.read 然后似乎违反了它的合同并返回JSONArray 而不是String。 Kotlin 似乎没有对此进行类型检查,并允许将 JSONArray 存储在 Map&lt;String, String?&gt; 中。

除非我尝试将myMap["key"] 用作String,否则不会产生错误

注意

这个问题可以通过将读取json的行改为:

myMap["key"] = ObjectMapper().writeValueAsString(JsonPath.read<String>(arrayString, "$"))

编辑

不使用json疯狂的代码示例:

fun <T> myFun(): T {
  return 7 as T
}

fun test() {
  val map = HashMap<String, String?>()
  map["key"] = myFun<String>()
  println(map["key"])
  println(map["key"]!!::class.java.name)
}

returns:
7
java.lang.Integer

【问题讨论】:

  • 您有一个字符串列表并试图将其解析为字符串。 kotlin 或 java 应该如何处理这个问题?引用自docsWhen using JsonPath in java its important to know what type you expect in your result.
  • @aweson 我希望 kotlin 说“嘿,你只是试图将 JSONArray 存储到需要字符串的地图中”或 JsonPath 说“嘿,你请求了一个字符串,但这不是一个字符串”。在我发现这一点的系统中,用户可以传入一组路径并期望返回 key=path 和 value=json 字符串的映射。我不知道路径上的类型。我在这里感兴趣的问题不是我的错误代码,而是 kotlin 允许将非字符串类型成功存储在字符串映射中。
  • JsonPath 承诺会返回 String。它撒谎了,因为它无法访问您请求的实际类型(类型擦除),而不是检查结果是否为String,它只是返回一个对象并说“您要求 T - 这是一些东西我找到了,希望它是 T"。
  • 我对 JsonPath 撒谎没意见(尽管我觉得不应该这样做),但我觉得当我们使用 map 的 put 方法时,应该定义类型地图就像“等一下。那不是字符串”。使用泛型,我基本上可以滥用 map 并将其视为 map
  • "等一下。那不是字符串" - 如果 Map 能够执行此检查,为什么 JsonPath 无法检查它将返回的对象的类型?这就是泛型的实现方式。您在运行时无权访问泛型类型。

标签: generics kotlin


【解决方案1】:

Generic type information is erased at runtime.

所以基本上只是把你更新的例子翻译成运行时的样子(简化):

fun myFun(): Any = 7

fun test() {
  val map = HashMap<Any, Any?>()
  map["key"] = myFun()
  println(map["key"])
  println(map["key"]!!::class.java.name) // of course: Integer!
}

还要注意,您在这里使用了unchecked cast(很可能JsonPath.read 也在使用它),这基本上是编译器没有真正机会发现问题的原因。或者换句话说,通过使用unchecked cast (7 as T) 你基本上对编译器说“嘿......我知道我在做什么,不要打扰”......所以它不会打扰; -)

【讨论】:

  • 所以听起来像泛型实际上只将类型分配给使用泛型在编译时打开的对象的 val 和 var,但对象本身在运行时忽略(或删除)其泛型类型时间
  • 我不明白你评论的第一部分......但我仍然试图澄清......编译器使用泛型类型,只要它可以使用并且通常可以帮助你编写类型安全的代码...但是一旦您使用未经检查的强制转换之类的东西(如果您真的不知道自己在做什么,则更是如此),那么泛型类型确实会受到伤害...然后您就可以拥有任何东西在那里,编译器永远不会警告你(除了未经检查的演员表)......所以作为一个重要的旁注:只要你可以:省略未经检查的演员表......或注意后果......
  • 我的意思是如果我有val something = myFun&lt;String&gt;()something 将是字符串类型。然后在将 Int 传递给它时在运行时出错。
  • 是的,没错。使用您的myFun 函数,它将在运行时抛出ClassCastException,因为7 (Int) 不能转换为String。关于var:如果您没有将something 的类型指定为Any?,则重新分配在编译时和运行时也不会成功,因为第一次分配基本上会确定something 的类型。
【解决方案2】:

一个类似的问题被跟踪为https://youtrack.jetbrains.com/issue/KT-12451

目前 Kotlin 不会在将泛型方法调用的结果传递给另一个泛型方法之前检查它,即使在调用站点它知道泛型类型参数的具体类型。这种行为与 Java 中的行为相同,但仍有改进的余地。

【讨论】:

    猜你喜欢
    • 2014-08-01
    • 2022-10-07
    • 2018-07-07
    • 1970-01-01
    • 2017-03-19
    • 1970-01-01
    • 2023-03-31
    • 1970-01-01
    • 2015-11-28
    相关资源
    最近更新 更多