En el siguiente código, donde MyMap implementa de manera trivial Map by delegación a impl :
foo@host:/tmp$ cat Foo.kt class MyMap <K, V> (val impl : Map <K, V>) : Map<K, V> by impl { fun myGetValue (k: K) = impl.getValue(k) } fun main() { val my_map = MyMap(mapOf('a' to 1, 'b' to 2).withDefault { 42 }) println(my_map.myGetValue('c')) // OK println(my_map.getValue('c')) // ERROR }¿Por qué aparece el siguiente error en la segunda impresión?
foo@host:/tmp$ /path/to/kotlinc Foo.kt foo@host:/tmp$ /path/to/kotlin FooKt 42 Exception in thread "main" java.util.NoSuchElementException: Key c is missing in the map. at kotlin.collections.MapsKt__MapWithDefaultKt.getOrImplicitDefaultNullable(MapWithDefault.kt:24) at kotlin.collections.MapsKt__MapsKt.getValue(Maps.kt:344) at FooKt.main(Foo.kt:8) at FooKt.main(Foo.kt) at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method) at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:77) at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) at java.base/java.lang.reflect.Method.invoke(Method.java:568) at org.jetbrains.kotlin.runner.AbstractRunner.run(runners.kt:64) at org.jetbrains.kotlin.runner.Main.run(Main.kt:176) at org.jetbrains.kotlin.runner.Main.main(Main.kt:186) foo@bigdev:/tmp$Actualización: los resultados de la versión del compilador y del tiempo de ejecución son:
foo@host:/tmp$ kotlinc -version info: kotlinc-jvm 1.6.10 (JRE 17.0.1+12-LTS) foo@host:/tmp$ kotlin -version Kotlin version 1.6.10-release-923 (JRE 17.0.1+12-LTS) foo@host:/tmp$ javac -version javac 17.0.1 foo@host:/tmp$ java -version openjdk version "17.0.1" 2021-10-19 LTS OpenJDK Runtime Environment Corretto-17.0.1.12.1 (build 17.0.1+12-LTS) OpenJDK 64-Bit Server VM Corretto-17.0.1.12.1 (build 17.0.1+12-LTS, mixed mode, sharing)Aunque hubiera esperado que su código funcionara para ser honesto, esto podría ser un error , pero tendríamos que mirar el código de bytes producido.
En la documentación dice (énfasis mío):
Este valor predeterminado implícito se usa cuando el mapa original no contiene un valor para la clave especificada y se obtiene un valor con la función Map.getValue , por ejemplo, cuando las propiedades se delegan al mapa.
El conflicto de "contratos" proviene de la interfaz del Map real, que dice:
Returns the value corresponding to the given [key], or null if such a key is not present in the map.El contrato predeterminado de mapas debe cumplir con esto, por lo que "solo" puede devolver un valor nulo cuando una clave no existe.
Encontré una discusión sobre esto en los foros de Kotlin .
Esto ocurre debido a la forma ligeramente inesperada en la que se implementa withDefault . El contenedor que produce withDefault no anula getValue() ya que esto es imposible porque getValue() es una función de extensión. Desafortunadamente, lo que tenemos en cambio es un antipatrón OOP clásico: getValue() is una verificación para ver si se está llamando en la interfaz MapWithDefault interna, y solo usa el valor predeterminado si ese es el caso. No veo ninguna forma de que pudieran haber evitado esta situación sin romper el contrato del Mapa.
myGetValue llama a getValue en el delegado subyacente, que es un MapWithDefault , por lo que funciona bien.
getValue en su instancia de MyMap fallará en la verificación interna is MapWithDefault porque MyMap no es un MapWithDefault , aunque su delegado sí lo sea. Los delegados de otros tipos no se propagan hasta la clase que los delega, lo cual tiene sentido. Por ejemplo, si delegamos en un MutableMap, es posible que queramos que la clase se considere solo un mapa de solo lectura.