Kotlin Map:为什么没有 toHashMap()?

Kotlin Map: why is there no toHashMap()?

在 Kotlin 中,Map class 有 toLinkedMap()toSortedMap() 扩展方法。

但是为什么没有toHashMap()方法呢?事实上,许多 stdlib 方法的结果 Map 实现是 LinkedHashMap,但在我的代码中将其转换为 HashMap 会使我依赖于不好的实现。

引入这样的方法可以让开发人员免于深入研究实现,而对于当前的实现,它只会执行转换。

我的用例是:

val matchesInClass: HashMap<MessageClass, HashMap<Int, Int>>

//...

for ((cl, matches) in matchesInClass) {
    matchesInClass[cl] = matches.filterKeys { it !in banned } //error: not a HashMap
}

当我使用 HashMap(matches.filterKeys { it !in banned }) 时,它会导致创建新地图的开销,我很乐意避免这种情况。

那么,这是设计使然吗?

我同意 stdlib 中的这个地方不方便。但是这样的功能toHashMap()要怎么实现呢?

如果底层映射是 HashMap,最简单的实现会进行强制转换 as HashMap,否则进行转换。因此,在更改实现的情况下,该代码的性能将发生巨大变化。太糟糕了。

所以,恕我直言,我更喜欢不安全的转换,如果 stdlib 以一种奇怪的方式发生变化并提醒我这一点,它就会失败。

无论如何,随时欢迎您在 YouTrack http://youtrack.jetbrains.com/issues/KT 中将此问题作为工单报告。您在这里对问题进行了很好的描述,所以应该不会花很长时间。

如何像这样过滤它:

val filtered = matchesInClass.mapValues { it.value.filterKeys { it !in banned } }

为什么? 很难回答,但搜索 Kotlin issue tracker 或向 Kotlin issue tracker 添加工单将为您提供答案,并在状态发生变化时通知您。似乎缺少 toHashMap 因为 toHashSet 在那里(截至 1.0 BETA 4)。

注意: LinkedHashMap 是默认值,因为 kotlin 希望在您从 ListSet 再到 [=13= 时保持元素的顺序] 到 MapList 并确保事物仍处于相对顺序。

无论如何,在此期间,这里有一个自定义 toHashMap 给你:

public fun <K, V> Iterable<Pair<K, V>>.toHashMap(): Map<K, V>
    = HashMap<K, V>(collectionSizeOrNull() ?: 16).apply { putAll(this@toHashMap) }

您可以使用类似于 toMap 函数的逻辑来选择最佳初始地图大小,或者不设置它并相信默认的增长实现。

Kotlin 被构建为易于扩展。如果所有东西都放在 stdlib 中,那么使用 Android 等受限设备的人会抱怨它太大了,实际上人们要求的大多数东西都是小怪癖,就是将几行代码放入您自己的自定义库中。

这不是您最初问题的答案,而是您用例的替代解决方案。

您在此处尝试过滤 HashMap<Int, Int> 并将其分配回从中获取的地图:

matchesInClass[cl] = matches.filterKeys { it !in banned } //error: not a HashMap

如果您不需要保持 matches HashMap 完整,您可以通过在其 keys 可变集上调用 removeAll 函数来就地过滤它:

val matchesInClass: HashMap<MessageClass, HashMap<Int, Int>>

//...

for (matches in matchesInClass.values) {
    matches.keys.removeAll { it in banned } // notice the inverted condition
}

这比创建过滤副本更有效率。

另一种选择是使用 Map.filterTo 提供所需类型的空地图:

for (entry in matchesInClass) {
    entry.setValue(entry.value.filterTo(HashMap()) { it !in banned })
}