Почему Котлин не позволяет ковариантной 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>
}