简体   繁体   English

为什么“Map”类的“get”方法可以在没有编译错误的情况下发送不相关的键?

[英]Why does 'get' method of class 'Map' enable sending a not relevant key without a compilation error?

I just had a bug in my program, but i don't understand how the program was compiled at all!我只是在我的程序中有一个错误,但我根本不明白该程序是如何编译的!

I have the following variable:我有以下变量:

gamesPerCountriesMap: MutableMap<Long, MutableMap<Long, MutableList<AllScoresGameObj>>>?

and i had the following line of code:我有以下代码行:

var gamesList = gamesPerCountriesMap?.get(countryItem.id)?.get(competitionItem)

the correct line should be:正确的行应该是:

var gamesList = gamesPerCountriesMap?.get(countryItem.id)?.get(competitionItem.id)

i have looked at the prototype of the Map class and the method is declared as following:我查看了 Map 类的原型,该方法声明如下:

public inline operator fun <@kotlin.internal.OnlyInputTypes K, V> Map<out K, V>.get(key: K): V?公共内联运算符 fun <@kotlin.internal.OnlyInputTypes K, V> Map<out K, V>.get(key: K): V?

As we can see it can get K and it's subtype, but competitionItem which is an instacne of class CompetitionObj isn't inherit the Long class.正如我们所看到的,它可以获取 K 并且它是子类型,但是作为 CompetitionObj 类的 instacne 的竞争项并没有继承Long类。 So why the compiler didn't prevent this error?那么为什么编译器没有阻止这个错误呢? I solved the issue but i am very couries of what is didn't prevent the code from being compiled?我解决了这个问题,但我很清楚什么没有阻止代码被编译?

There are two get methods for Map interface. Map接口有两种get方法。

One is defined directly in the body of the interface: 一种是直接在接口主体中定义的:

Map<K, V> { fun get(key: K): V? }

Another (which you cite in your question) - as an extention function: 另一个(您在问题中引用) - 作为扩展函数:

fun <K, V> Map<out K, V>.get(key: K): V?

The overload resolution on call depends on whether or not you explicitly specify generic parameters, and on relationship between type of map K generic parameter & type of passed key argument:调用时的重载解析取决于您是否明确指定泛型参数,以及映射K泛型参数类型与传递的key参数类型之间的关系:

  1. If you specify explicit generic parameter , the second version will be called (and it wouldn't have compiled in your case, if you've wrote .get()<Long, MutableList<AllScoresGameObj>.(competitionItem) , although .get()<CompetitionObj, MutableList<AllScoresGameObj>.(competitionItem) ) would've worked, cause there is unsafe cast inside this get overload).如果您指定显式泛型参数,则将调用第二个版本(如果您编写了.get()<Long, MutableList<AllScoresGameObj>.(competitionItem) ,尽管.get()<CompetitionObj, MutableList<AllScoresGameObj>.(competitionItem) ) 会起作用,因为在这个get重载中有不安全的转换)。
  2. If you omit explicit generic parameter , passing as a key :如果省略显式泛型参数,则作为key传递:
    1. an instance of K type (or its subtype) - the first overload will be called K类型(或其子类型)的实例 - 第一个重载将被调用
    2. anything else - the second overload (cause the first overload will not compile).其他任何东西 - 第二个重载(因为第一个重载不会编译)。 In this case Kotlin will try to infer omitted generic parameters, so that original map could be represented as a Map<out inferredK, V> and inferredK type parameter was a supertype of passed key argument.在这种情况下,Kotlin 将尝试推断省略的泛型参数,因此原始地图可以表示为Map<out inferredK, V>并且inferredK类型参数是传递的key参数的超类型。 Eventually, it will come up with inferredK = Any .最终,它会得到inferredK = Any Indeed Any is a supertype of everything, and it's perfectly legal to do val x: Map<out Any, V> = mapOf<K, V>() for any K .事实上Any是所有事物的超类型,对任何K执行val x: Map<out Any, V> = mapOf<K, V>()是完全合法的。 Actually compiler realizes that this is a trivial solution and issues a compilation warning Type inference failed. The value of the type parameter K should be mentioned in input types (argument types, receiver type or expected type). Try to specify it explicitly.实际上编译器意识到这是一个微不足道的解决方案并发出编译警告Type inference failed. The value of the type parameter K should be mentioned in input types (argument types, receiver type or expected type). Try to specify it explicitly. Type inference failed. The value of the type parameter K should be mentioned in input types (argument types, receiver type or expected type). Try to specify it explicitly. (I believe in your case this warning should've been too). (我相信在你的情况下,这个警告也应该如此)。 Why this still works in runtime?为什么这在运行时仍然有效? Because of type erasure .因为类型擦除

So, why this overloaded version was added to stdlib?那么,为什么将这个重载版本添加到 stdlib 中呢? Don't know for sure, maybe for some legal cases like:不确定,也许对于某些法律案件,例如:

val k : Base = Derived()
val v = mapOf<Derived, String>().get(k) // will call overloaded variant; will infer K as Base

without this overload you'd have to manually cast back:如果没有这个重载,你就必须手动转换:

val v = mapOf<Derived, String>().get(k as Derived) 

Why two get methods?为什么有两个get方法?

  1. Kotlin wants to inherit Java type system. Kotlin 想要继承Java 类型系统。
val x: kotlin.collections.List<String>= java.util.ArrayList<>()
  1. Kotlin type system is different. Kotlin 类型系统是不同的。 One of the differences is declaration-site variance差异之一是声明站点差异

Having拥有

open class A
class B

It is illegal in Java在 Java 中是非法的

List<A> x = new ArrayList<B>();

But absolutely normal in Kotlin但在 Kotlin 中绝对正常

val x: List<A> = ArrayList<B>()

As of the Map example I would expect至于我期望的Map示例

Error: Type inference failed.错误:类型推断失败。 The value of the type parameter K should be mentioned in input types (argument types, receiver type or expected type).类型参数 K 的值应在输入类型(参数类型、接收器类型或预期类型)中提及。 Try to specify it explicitly尝试明确指定它

whenever type inference is broken since Kotlin 1.0.0自 Kotlin 1.0.0 起类型推断被破坏时

If it's really a problem, it would be great to see a code paste and kotlin compiler version you are using.如果真的有问题,很高兴看到您正在使用的代码粘贴和 kotlin 编译器版本。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM