I am new to Kotlin. I am following along a tutorial where the GUI portion involves this code snippet:
sampleList.addMouseListener(object: MouseAdapter() {
override fun mouseClicked(mouseEvent: MouseEvent?) {
if (mouseEvent?.clickCount == 2) {
launchSelectedSample()
}
}
})
mouseEvent
is obviously something nullable. I am used to, in previous coding experience, changing a line like mouseEvent?.clickCount == 2
to mouseEvent?.clickCount > 1
(or maybe >=2
), to ensure there is no corner case where the clicks happen so quickly that it jumps from 1 to 3, or something similar.
So, I switched this code to:
sampleList.addMouseListener(object: MouseAdapter() {
override fun mouseClicked(mouseEvent: MouseEvent?) {
if (mouseEvent?.clickCount >= 2) {
launchSelectedSample()
}
}
})
Upon making the switch (changing ==2
to >=2
), I received the following error from IntelliJ:
Operator call corresponds to a dot-qualified call 'mouseEvent?.clickCount.compareTo(2)' which is not allowed on a nullable receiver 'mouseEvent?.clickCount'.
This raised 2 questions for me:
==2
work fine, but >=2
does not work? (I tried >1
, which gave the same error as >=2
.)I like the idea of ensuring null
s don't screw things up during runtime, but I kinda wish if Kotlin just got rid of null
values altogether and did something like Rust or Haskell. (I do like what I've seen of Kotlin so far, though.)
TL;DR Kotlin has the === operator for referential integrity and none of true , false or null references the same thing. Don't forget that, when comparing nullable booleans.
As you've found, Kotlin's equality operators (==
and !=
) can handle nulls, while the order comparison operators (<
, <=
, >
, >=
) can't.
This is probably because it's obvious what equality checks should mean for nulls — two nulls are clearly equal, and a non-null value should never equal a null — while it's not at all clear what it should mean for order comparisons. (If null isn't < 0, does that mean null >= 0? If not, you no longer have a well-defined ordering.)
This is reflected in the implementation: Any
has an equals()
method, indicating that all objects can be checked for equality. (Kotlin's documentation makes it explicit, as does that for the underlying Java method, that non-null objects must never equal null.) And Kotlin's implementation of the ==
and !=
operators explicitly checks for nulls. (a == b
translates to what you have to spell out in Java: a == null ? b == null : a.equals(b)
.)
But order comparison is handled differently. It uses the Comparable
interface: only types with a ‘natural ordering’ implement that; those that don't, can't be compared in that way. Since null can't implement any interfaces, it can't have a natural ordering, and the compiler prevents you trying the comparison. (Kotlin's documentation doesn't make this explicit, because the parameter is non-nullable; but that for the underlying Java interface says that such a comparison should return a NullPointerException.)
As to how you should handle this, the Elvis operator is probably the most concise solution:
if (mouseEvent?.clickCount ?: 0 >= 2)
If mouseEvent
is not null, this will get its clickCount
; otherwise, the safe-call ?.
will give the null directly, and then the ?:
will substitute 0. (That would also happen if the clickCount
held null, though that shouldn't be possible.) In every case, you end up with a non-nullable integer that can safely be compared with 2.
Of course, in practice, nothing should ever be calling a listener method and passing a null event. (I can't recall ever allowing for that back when I used to write Java Swing code for a living, or hitting any problems as a result.) So a simpler alternative might be declaring the parameter as non-nullable. But handling the null properly is just that little bit safer; and in this case, it doesn't add much extra code. So it's up to you!
a little bit more kotlin style can be this:
mouseEvent?.clickCount?.let{
if(it >= 2) {
launchSelectedSample()
}
}
even more functional style:
mouseEvent?.clickCount?.takeIf { it >= 2 }?.let { launchSelectedSample() }
What behavior from mouseEvent?.clickCount >= 2
do you expect when mouseEvent == null
?
You could transform your mouseEvent?.clickCount
to NotNull using elvis (?:
) operator:
val clickCount = mouseEvent?.clickCount ?: 0
if (clickCount >= 2) {
launchSelectedSample()
}
In this case mouseEvent?.clickCount
will be 0 if mouseEvent
is null
==2
works because mouseEvent?.clickCount
considered by Kotlin as null
and comparison null == 2
is correct unlike null >= 2
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With