【问题标题】:Why kotlin doesn't allow covariant mutablemap to be a delegate?为什么 kotlin 不允许协变 mutablemap 成为委托?
【发布时间】:2017-11-23 17:28:06
【问题描述】:

我是 Kotlin 的新手。 当我学习Storing Properties in a Map。我尝试以下用法。

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

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

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

前两个都可以,最后一个失败。 加上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

正如我们所见,它会导致NullPointerException。

为什么地图委托上不允许逆变?

为什么 kotlin 没有给我编译错误?

【问题讨论】:

  • 你不应该在那里使用逆变,因为地图 产生 值。您没有收到编译器警告的事实可能是一个错误。实际上out 是协变的,in 是逆变的。出于某些的原因,使用in 会产生我期望使用out 的代码。

标签: delegates jvm kotlin generic-variance


【解决方案1】:

简短回答:这不是编译器中的错误,而是operator getValue() 的签名如何为MutableMap 声明的不幸结果。

长答案: delegating properties 映射是可能的,因为标准库中有以下三个运算符函数:

// for delegating val to read-only map
operator fun <V, V1: V> Map<in String, @Exact V>.getValue(thisRef: Any?, property: KProperty<*>): V1

// for delegating var to mutable map
operator fun <V> MutableMap<in String, in V>.getValue(thisRef: Any?, property: KProperty<*>): V

operator fun <V> MutableMap<in String, in V>.setValue(thisRef: Any?, property: KProperty<*>, value: V)

这里选择了MutableMap 接收器的使用站点差异,以便可以将某种类型的属性委托给可以存储其超类型的映射:

class Sample(val map: MutableMap<String, Any>) {
    var stringValue: String by map
    var intValue: Int by map
}

不幸的是,当您尝试使用投影出的MutableMap&lt;String, out String&gt; 作为val 属性的代表并因此作为getValue 运算符的接收者时,会发生以下情况:

  • 选择MutableMap&lt;in String, in V&gt;.getValue 重载,因为它具有更具体的接收器类型。
  • 由于接收器映射具有out String 类型参数投影,因此不知道它的实际类型参数是什么(它可以是MutableMap&lt;..., String&gt; 或MutableMap&lt;..., SubTypeOfString&gt;),所以唯一安全的选择是假设它是Nothing ,它是所有可能类型的子类型。
  • 此函数的返回类型声明为V,已推断为Nothing,编译器插入一个检查,确认实际返回值的类型为Nothing,这应该总是失败,因为不能'不是Nothing 类型的值。此检查在字节码中类似于 throw null。

我已经打开了一个问题 KT-18789 来看看我们可以用这个操作符函数的签名来做什么。

UPD:签名已在 Kotlin 1.2.20 中修复

同时,您可以将MutableMap 强制转换为Map,以便选择getValue 的第一个重载:

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

【讨论】:

    【解决方案2】:

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

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

    使用MutableMap&lt;String, in String&gt;,相当于Java 的Map&lt;String, ? super String&gt;,它使用逆变。

    使用MutableMap&lt;String, out String&gt;,相当于使用协方差的Java的Map&lt;String, ? extends String&gt;。

    (你把两者混在一起了)

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

    Delegation by map 使用第二种通用​​类型的地图作为生产者(您可以从地图中得到 out 的东西),因此不应使用 MutableMap&lt;String, in String&gt;,因为它的第二个参数是一个消费者(把东西放进去)。

    由于某种原因,编译器会在 MutableMap&lt;String, in String&gt; 的情况下生成 MutableMap&lt;String, out String&gt; 所需的代码,这是错误的,如您在此示例中所见:

    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
    }
    

    您将得到一个类转换异常,因为 VM 试图将 StringBuilder 视为 String。但是您不使用任何显式强制转换,因此它应该是安全的。

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

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

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

    【讨论】:

    • 谢谢。所以这只是一个错误。
    猜你喜欢
    • 1970-01-01
    • 2017-11-24
    • 2018-03-05
    • 1970-01-01
    • 1970-01-01
    • 2014-04-25
    • 1970-01-01
    • 1970-01-01
    • 2012-05-06
    相关资源
    最近更新 更多