Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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

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:

  1. Why does ==2 work fine, but >=2 does not work? (I tried >1, which gave the same error as >=2.)
  2. What is the right way to handle a corner case where, what I really want is anything greater than 1?

I like the idea of ensuring nulls 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.)

like image 514
Mike Williamson Avatar asked Mar 28 '19 16:03

Mike Williamson


People also ask

Does null equal false Kotlin?

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.


3 Answers

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!

like image 185
gidds Avatar answered Nov 18 '22 08:11

gidds


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() }
like image 29
Kemal Cengiz Avatar answered Nov 18 '22 08:11

Kemal Cengiz


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

like image 41
Taras Parshenko Avatar answered Nov 18 '22 06:11

Taras Parshenko