I'm new to Kotlin. When I learn Storing Properties in a Map. I try following usage.
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
}
The first two are both work, the last one failed.
With out
modifier, the bytecode of getName
like this:
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
As we can see, it will cause a NullPointerException
.
Why contravariant is not allowed on a map delegate?
And why kotlin doesn't give me a compile error?
Android Dependency Injection using Dagger with Kotlin Kotlin supports “delegation” design pattern by introducing a new keyword “by”. Using this keyword or delegation methodology, Kotlin allows the derived class to access all the implemented public methods of an interface through a specific object.
A delegate is just a class that provides the value for a property and handles its changes. This allows us to move, or delegate, the getter-setter logic from the property itself to a separate class, letting us reuse this logic.
Kotlin MutableMap is an interface of collection frameworks which contains objects in the form of keys and values. It allows the user to efficiently retrieve the values corresponding to each key. The key and values can be of the different pairs like <Int, String>, < Char, String>, etc.
The function put() and putAll() are used to add the elements in the MutableMap. put() function adds the single element at a time where as putAll() function adds the collection type elements in the MutableMap. For example: fun main(args: Array<String>) {
Short answer: it's not a bug in the compiler, but rather an unfortunate consequence of how the signature of operator getValue()
is declared for MutableMap
.
Long answer: delegating properties to maps is possible because of the following three operator functions in the standard library:
// 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)
Here the use-site variance of the MutableMap
receiver is chosen so that one could delegate a property of some type to a map which can store its supertype:
class Sample(val map: MutableMap<String, Any>) {
var stringValue: String by map
var intValue: Int by map
}
Unfortunately, when you try to use an out-projected MutableMap<String, out String>
as a delegate for a val
property and consequently as a receiver of getValue
operator, here what happens:
MutableMap<in String, in V>.getValue
overload is choosen, because it has more specific receiver type.out String
type argument projection it's unknown what its actual type argument is (it can be a MutableMap<..., String>
or a MutableMap<..., SubTypeOfString>
), so the only safe option is to assume it is Nothing
, which is a subtype of all possible types.V
which has been inferred to Nothing
, and the compiler inserts a check that the actual returned value is of type Nothing
, which should always fail, as there couldn't be a value of type Nothing
. This check looks like throw null
in the bytecode.I've opened an issue KT-18789 to see what we can be done with the signature of this operator function.
UPD: The signature was fixed in Kotlin 1.2.20
Meanwhile as a workaround you can cast the MutableMap
to Map
, so that the first overload of getValue
is chosen:
class User(val map: MutableMap<String, out String>) {
val name: String by map as Map<String, String>
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With