Kotlin:相等比较似乎可以为空,但大于比较则不然

Kotlin: equal comparison seems OK on nullable, but greater than comparison is not

我是 Kotlin 新手。我正在学习一个教程,其中 GUI 部分涉及以下代码片段:

    sampleList.addMouseListener(object: MouseAdapter() {
        override fun mouseClicked(mouseEvent: MouseEvent?) {
            if (mouseEvent?.clickCount == 2) {
                launchSelectedSample()
            }
        }
    })

mouseEvent 显然是 可空的 。在以前的编码经验中,我习惯于将 mouseEvent?.clickCount == 2 之类的行更改为 mouseEvent?.clickCount > 1 (或者可能是 >=2), 以确保没有极端情况 其中点击发生得如此之快,以至于它从 1 跳到 3,或类似的东西。


所以,我将此代码切换为:

    sampleList.addMouseListener(object: MouseAdapter() {
        override fun mouseClicked(mouseEvent: MouseEvent?) {
            if (mouseEvent?.clickCount >= 2) {
                launchSelectedSample()
            }
        }
    })

进行切换(==2 更改为 >=2)后,我从 IntelliJ 收到以下错误:

Operator call corresponds to a dot-qualified call 'mouseEvent?.clickCount.compareTo(2)' which is not allowed on a nullable receiver 'mouseEvent?.clickCount'.

这给我提出了 2 个问题:

  1. 为什么==2可以正常工作,而>=2却不行? (我尝试了 >1,它给出了与 >=2 相同的错误。)
  2. 处理极端情况的正确方法是什么,我真正想要的是大于 1 的值?

我喜欢确保 nulls 在运行时不会搞砸的想法,但我有点希望 Kotlin 完全摆脱 null 值并做一些像 Rust 或 Haskell。 (不过,到目前为止,我确实喜欢我所看到的 Kotlin。)

mouseEvent == null 时,您期望 mouseEvent?.clickCount >= 2 有什么行为?

您可以使用 elvis (?:) 运算符将 mouseEvent?.clickCount 转换为 NotNull:

val clickCount = mouseEvent?.clickCount ?: 0
if (clickCount >= 2) {
    launchSelectedSample()
}

在这种情况下,如果 mouseEvent 为 null

,则 mouseEvent?.clickCount 将为 0

==2 有效是因为 mouseEvent?.clickCount 被 Kotlin 视为 null 并且比较 null == 2null >= 2

不同是正确的

如您所见,Kotlin 的等式运算符(==!=)可以处理空值,而顺序比较运算符(<<=>, >=) 不能。

这可能是因为对于空值,意味着 什么相等性检查是显而易见的 — 两个空值显然相等,非空值永远不应该等于空值 — 而事实并非如此完全清楚它对订单比较意味着什么。 (如果 null 不 < 0,是否意味着 null >= 0?如果不是,则您不再有明确定义的顺序。)

这体现在实现上:Any有一个equals()方法,表示可以检查所有对象是否相等。 (Kotlin 的 documentation makes it explicit, as does that for the underlying Java method,非 null 对象绝不能等于 null。)Kotlin 对 ==!= 运算符的实现明确检查空值。 (a == b 转换为您必须在 Java 中拼出的内容:a == null ? b == null : a.equals(b)。)

但是订单比较的处理方式不同。它使用 Comparable 接口:只有具有“自然排序”的类型才能实现;那些没有的,不能以这种方式进行比较。由于 null 不能实现任何接口,它不能有自然顺序,编译器会阻止你尝试比较。 (Kotlin 的 documentation doesn't make this explicit, because the parameter is non-nullable; but that for the underlying Java interface says 这样的比较应该 return 一个 NullPointerException。)

至于你应该如何处理这个问题,Elvis 运算符可能是最简洁的解决方案:

if (mouseEvent?.clickCount ?: 0 >= 2)

如果mouseEvent不为空,这将得到它的clickCount;否则,安全调用 ?. 将直接给出空值,然后 ?: 将替换为 0。(如果 clickCount 保持为空也会发生这种情况,尽管这不应该是可能。)在每种情况下,您最终都会得到一个可以安全地与 2 进行比较的不可为 null 的整数。

当然,在实践中,不应该调用侦听器方法并传递空事件。 (我不记得以前我曾经以编写 Java Swing 代码为生,也没有因此遇到过任何问题。)因此,一个更简单的替代方法可能是将参数声明为不可空的。但是正确处理 null 只是稍微安全一点;在这种情况下,它不会添加太多额外的代码。所以这取决于你!

多一点 kotlin 风格可以是这样的:

mouseEvent?.clickCount?.let{ 
  if(it >= 2) {
    launchSelectedSample()
  }
}

更实用的风格:

mouseEvent?.clickCount?.takeIf { it >= 2 }?.let { launchSelectedSample() }