javakotlinreferenceequalitystructural-equality

Why does Kotlin use == for structural equality and introduce === for referential equality


In general every design decision in Kotlin feels like it's both great in its own right and offers a nice transition from Java. As a Java developer you can start coding in it thinking of Kotlin as a more concise Java with less boilerplate, then smoothly move into more advanced aspects like functional programming.

The one thing I'm wondering about however is why its designers decided to make == behave identically to equals and then introduce === for the referential equality check. I can imagine trying to bring other Java developers into the fold, having them see your Kotlin code and thinking "oh no, there are reference checks all over the place where it should be an equals call!"

What's the thought process for moving away from the Java convention here? Just to make it clear, I understand perfectly well what the difference is between == or equals and === in Kotlin, I'm just wondering why.


Solution

  • The major reason is probably that object equality is checked much more often than object identity, so it should be at least as easy.

    I haven't seen a definitive statement on this.  But there's a pointer in the book Kotlin In Action, by members of the Kotlin team.  Section 4.3.1, introducing the == operator, first describes Java's comparisons and says that:

    in Java, there's the well-known practice of always calling equals, and there's the well-known problem of forgetting to do so.

    In Java, checking object identity is easy:

    if (firstObj == secondObj)
    

    But checking object equality is longer and rather less clear:

    if (firstObj.equals(secondObj))
    

    — or rather, if you don't want to risk a NullPointerException:

    if ((firstObj == null) ? (secondObj == null) : firstObj.equals(secondObj))
    

    You can see how much more of a pain that is to type, and to get right.  (Especially when one of those objects is an expression with side-effects…)

    So it's easy to forget the difference, or not be bothered, and use == instead.  (Which is likely to cause bugs that are subtle, hard to spot, and bite intermittently.)

    Kotlin, however, makes the most common operation the easier one: its == operator checks object equality using equals(), and takes care of null-checking too.  This fixes Java's ‘problem of forgetting to do so’.

    (And although interoperability with Java code was clearly a major goal, JetBrains didn't restrict themselves to trying to look like Java; Kotlin borrows from Java where possible, but isn't afraid to change things for the better.  You can see that in the use of val and var and trailing types for declarations, the different defaults for scoping and openness, the different ways variance is handled, &c.)

    One of Kotlin's motivations was to fix many of the problems in Java.  (In fact, JetBrains' comparison of the languages starts by listing ‘Some Java issues addressed in Kotlin’.)  So it seems likely that this is a major reason behind the change.