为什么kotlin不允许协变mutablemap成为委托?

Dea*_* Xu 7 delegates jvm generic-variance kotlin

我是Kotlin的新手.当我在地图中学习存储属性时.我尝试以下用法.

class User(val map: MutableMap<String, String>) {
    val name: String by map
}
Run Code Online (Sandbox Code Playgroud)

class User(val map: MutableMap<String, in String>) {
    val name: String by map
}
Run Code Online (Sandbox Code Playgroud)

class User(val map: MutableMap<String, out String>) {
    val name: String by map
}
Run Code Online (Sandbox Code Playgroud)

前两个都是工作,最后一个都失败了.使用out修饰符,字节码getName如下:

  public final java.lang.String getName();
     0  aload_0 [this]
     1  getfield kotl.User.name$delegate : java.util.Map [11]
     4  astore_1
     5  aload_0 [this]
     6  astore_2
     7  getstatic kotl.User.$$delegatedProperties : kotlin.reflect.KProperty[] [15]
    10  iconst_0
    11  aaload
    12  astore_3
    13  aload_1
    14  aload_3
    15  invokeinterface kotlin.reflect.KProperty.getName() : java.lang.String [19] [nargs: 1]
    20  invokestatic kotlin.collections.MapsKt.getOrImplicitDefaultNullable(java.util.Map, java.lang.Object) : java.lang.Object [25]
    23  checkcast java.lang.Object [4]
    26  aconst_null
    27  athrow
      Local variable table:
        [pc: 0, pc: 28] local: this index: 0 type: kotl.User
Run Code Online (Sandbox Code Playgroud)

我们可以看到,它会导致一个NullPointerException.

为什么地图代表不允许使用逆变?

为什么kotlin不会给我编译错误?

Jor*_*nee 0

是的...编译器在这里肯定是错误的。(使用 Kotlin 版本 1.1.2-5 进行测试)

首先,在将属性委托给映射的情况下,您可以使用属性的名称在映射中查找它的值。

使用MutableMap<String, in String>, 相当于 Java 中Map<String, ? super String>使用逆变的

使用MutableMap<String, out String>, 相当于 JavaMap<String, ? extends String>使用协方差

(你把两者搞混了)

协变类型可以用作生产者。逆变类型可以用作消费者。(请参阅PECS。抱歉,我没有 Kotlin 特定的链接,但原则仍然适用)。

按映射委托使用映射的第二个通用类型作为生产者(您从映射中获取内容,因此不可能使用 a,MutableMap<String, in String>因为它的第二个参数是消费者(将内容放入其中)。

MutableMap<String, out String>由于某种原因,编译器在 a 的情况下生成 a 所需的代码MutableMap<String, in String>,这是错误的,如本例所示:

class User(val map: MutableMap<String, in String>) {
    val name: String by map
}

fun main(args:Array<String>){
    val m: MutableMap<String, CharSequence> = mutableMapOf("name" to StringBuilder())
    val a = User(m)

    val s: String = a.name
}
Run Code Online (Sandbox Code Playgroud)

您将得到一个类转换异常,因为虚拟机正在尝试将 aStringBuilder视为String. 但您不使用任何显式强制转换,因此它应该是安全的。

throw null不幸的是,它在 的有效用例中生成垃圾 ( ) out

在使用协方差()的情况下String并没有真正意义out,因为String它是最终的,但在不同类型层次结构的情况下,我能想到的唯一解决方法是手动修补字节码,这是一场噩梦。

我不知道是否有现有的错误报告。我想我们只能等到这个问题得到解决。