为什么这段代码在 Kotlin 中无效?

问题描述

我开始学习 Kotlin,我正在用这种语言编写我的第一行代码

我很惊讶这不能编译

fun sockMerchant(n: Int,ar: Array<Int>): Int {
    var sockets = HashMap<Int,Int>() 
    for (socket in ar)
    {
        if (sockets.containsKey(socket))
        {
            sockets[socket]++; // <= Error here "No set method providing array access"
        }
    }
}

我看到一个 report 说这是一个编译器错误......但我很惊讶它存在这么久没有解决,所以我可能会误解一些东西。

我必须这样做

sockets[socket] = sockets[socket]!!.inc();

这是多么可怕和超级冗长。

我来自 c# 世界,我一直在做 [XXX]++!! Kotlin 中有什么问题?

解决方法

问题是 get 所在的 sockets[socket] 的返回类型为 V?;如果映射中没有 socket 的值,它可以返回 null。即使您在那里有 if (sockets.containsKey(socket)),编译器也不知道(例如,如果 sockets 是共享的,在 if 之后如何停止另一个线程将其设置为 null)。那么当您在 null 上调用 ++ 时会发生什么?

替代冗长的另一种方法是:

sockets.merge(socket,1,Int::plus)
,

一般来说,Obj[XXX]++ 习语在 Kotlin 中是不被禁止的。 这里的问题是 sockets[socket] 调用的结果具有可空类型 (Int?) 并且后缀增量表达式仅适用于不可空类型(因为在幕后它进行了 .inc() 调用)。

看看这个假设的例子:

class HashMapWithNonNullableGet<K,V> : HashMap<K,V>() {
    override fun get(key: K) : V = super.get(key)!!
}

fun sockMerchant(n: Int,ar: Array<Int>) {
    val sockets = HashMapWithNonNullableGet<Int,Int>()
    for (socket in ar) {
        if (sockets.containsKey(socket)) {
            sockets[socket]++ // sockets[socket] returns Int,so no errors here
        }
    }
    println(sockets)
}

在您的情况下,最好将循环体重构为:

for (socket in ar) {
    sockets.computeIfPresent(socket) { _,v -> v + 1 }
}