3

在下面的代码中,其中MyMap简单地实现Map by了委托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
}

为什么我在第二个 println 上收到以下错误?

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$

更新:编译器和运行时版本输出是:

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)
4

2 回答 2

2

虽然老实说我希望您的代码能够正常工作,但这可能是错误,但我们必须查看生成的字节码。

文档中它说(强调我的):

当原始地图不包含指定键的值并且使用 Map.getValue 函数获得值时使用此隐式默认值,例如当属性被委托给地图时。

“合同”的冲突来自实际的Map界面,它说:

Returns the value corresponding to the given [key], or null if such a key is not present in the map.

地图默认合约必须满足这一点,因此它可以“仅”在密钥不存在时返回 null。

我在 Kotlin 论坛中找到了一个关于此的讨论。

于 2022-02-03T09:01:48.693 回答
1

发生这种情况是因为withDefault实施方式有点出乎意料。生成的包装器withDefault不会覆盖getValue(),因为这是不可能的,因为getValue()它是一个扩展函数。所以不幸的是,我们所拥有的是一个经典的 OOP 反模式:检查getValue()is是否在内部MapWithDefault接口上被调用,并且只有在这种情况下才使用默认值。我看不出他们有什么办法可以在不违反地图合同的情况下避免这种情况。

myGetValue调用getValue底层委托,它是 a MapWithDefault,所以它工作正常。

getValue在您的实例上调用MyMap将无法通过内部is MapWithDefault检查,因为MyMapis not a MapWithDefault,即使它的委托是。其他类型的委托不会传播到委托给它的类,这是有道理的。就像我们委托给 MutableMap 一样,我们可能希望该类被视为只读 Map。

于 2022-02-03T15:28:32.820 回答