Почему Котлин не позволяет ковариантной mutablemap быть делегатом?

Я новичок в Котлин. Когда я научусь!--8-- > хранение свойств на карте. Я пытаюсь следовать использованию.

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.

почему контравариант не допускается на карте делегат?

и почему Котлин не дает мне ошибку при компиляции?

2 ответов


да... компилятор здесь определенно ошибается. (тестирование с Kotlin версии 1.1.2-5)

прежде всего, в случае делегирования свойства на карту вы используете имя свойства, чтобы найти его значение на карте.

С помощью MutableMap<String, in String>, эквивалентно Java Map<String, ? super String> использует контравариантность.

С помощью MutableMap<String, out String>, эквивалентно Java Map<String, ? extends String> использует ковариации.

(вы перепутал два)

ковариантный тип может использоваться в качестве производителя. В качестве потребителя можно использовать контравариантный тип. (См.Печ. извините,у меня нет конкретной ссылки Kotlin, но принцип все еще применяется).

делегирование по карте использует второй общий тип карты в качестве производителя (вы получаете вещи из карты), поэтому использовать MutableMap<String, in String> так как это второй параметр является потребителем (чтобы положить вещи в.)

по какой-то причине компилятор генерирует код, необходимый для MutableMap<String, out String> в случае 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
}

вы получите исключение класса cast, потому что VM пытается обработать StringBuilder как String. Но вы не используете никаких явных слепков, поэтому это должны будьте в безопасности.

к сожалению, он генерирует мусор (throw null) в допустимом случае использования out.

в случае String на самом деле нет смысла использовать ковариацию (out), поскольку String является окончательным, но в случае иерархии другого типа единственная работа, которую я могу придумать, - это вручную исправить байт-код, что является кошмаром.

я не знаю, есть ли существующий отчет об ошибке. Думаю, нам придется подождать, пока все не уладится.


короткий ответ:: это не ошибка в компиляторе, а скорее неудачное следствие того, как подпись operator getValue() объявляется для MutableMap.

ответ: делегирование свойства to maps возможно из-за следующих трех функций оператора в стандартной библиотеке:

// 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 receiver выбран так, чтобы можно было делегировать свойство некоторых введите на карту, которая может хранить свой супертип:

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

к сожалению, когда вы пытаетесь использовать out-projected MutableMap<String, out String> в качестве делегата на val свойство и, следовательно, как получатель getValue оператор, вот что происходит:

  • MutableMap<in String, in V>.getValue перегрузка выбрана, потому что она имеет более конкретный тип приемника.
  • так как карта получателя out String проекция аргумента типа неизвестно, каков его фактический аргумент типа (это может быть MutableMap<..., String> или MutableMap<..., SubTypeOfString>), поэтому единственный безопасный вариант-это предположить, что это Nothing, который является подтипом всех возможных видах.
  • возвращаемый тип этой функции объявляется как V что было выведено на Nothing, и компилятор вставляет проверку, что фактическое возвращаемое значение имеет тип Nothing, который всегда должен терпеть неудачу, так как не может быть значения типа Nothing. Этот чек выглядит как throw null в байт-код.

я открыл вопрос KT-18789 чтобы увидеть, что мы можем сделать с подписью Это функция оператора.

UPD: подпись была зафиксирована в Котлине 1.2.20

между тем, как обходной путь вы можете бросить MutableMap до Map, так что первая перегрузка - это:

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