It seems like there is a wrong explanation about '==' operator on the kotlinlang webpage


#1

https://kotlinlang.org/docs/reference/operator-overloading.html

The == operation is special: it is translated to a complex expression that screens for null 's. null == null is always true, and x == null for a non-null x is always false and won’t invoke x.equals() .

This seems wrong with kotlin 1.3 because when x is not null, I can see ‘x == null’ invokes x.equals(null) when I tested it - I used IntelliJ Community Edition with kotlin language version and API version of 1.3.
And the explanation on the kotlinlang website seems like different from that on github page.
(I’m not sure which version on github to attach as a link, but I think they’re mostly same.)

The == operation is special in two ways:

  • It is translated to a complex expression that screens for null 's, and null == null is true .
  • It looks up a function with a specific signature , not just a specific name . The function must be declared as

fun equals(other: Any?): Boolean


#2

Could you show the code where x == null invokes x.equals(null)?

The following example doesn’t exhibit such behavior:


class Foo {
    override fun equals(other: Any?): Boolean {
        println("Foo.equals($other) was called")
        return super.equals(other)
    }
}

fun test(x: Any?)  {
    val r1 = x == "string"
    val r2 = x == 1
    val r3 = x == null
}

fun main() {
    test(Foo())
}

#3

Thanks for the code.

I think I understood differently from what the document intended. I thought it were a matter of value, not a expression. In my code, I passed null value to a function and used “a == b” expression, not “a == null” expression
However I still feel this difference can be dangerous even though I don’t know it is intended or not.

data class TestClass(val a: Int) {
    override fun equals(other: Any?): Boolean {
        return true
    }
}

fun myEquals(a: Any?, b: Any?): Boolean  {
    return a == b
}
fun myEquals2(a: Any?, b: Any?) = a == b

fun main(args: Array<String>) {
    var nonNull = TestClass(7)
    println("equals: ${myEquals(nonNull, null)}")
    println("equals: ${myEquals(null, nonNull)}")
    println("equals: ${myEquals2(nonNull, null)}")
    println("equals: ${myEquals2(null, nonNull)}")
}

#4

The way how TestClass implements equals method in this example contradicts the contract of that method: from the principle of symmetry of equals for any non-null value x, x.equals(null) should return false.

The Kotlin compiler generates executable code counting on the fact that classes abide the contract when implementing equals method and doesn’t try to fix the consequences of violating that contract.


#5

You’re right. I don’t think my equals is implemented in a right way. I was testing the documented behavior of when equals is called, not behavior of equals itself.
(I wans’t testing the return value of the ‘==’ statement. Maybe my print statements made you confused.)

My point is “nonNull == null” and “nonNull == b” works differently when b is null.
I don’t think it matter that equals returns true or not in this issue. Even if I change it to return false which will make it symmetry, the behavior on calling equals is identical.

If you put null for b in the next function, you’ll see it without any statement removed by optimizer in the debugging mode.

fun testFoo(nonNull: Any?, b: Any?) {
    var k = 3
    if (nonNull == b) {
        k = 10
    }
    if (nonNull == null) {
        k = 10
    }
}

----- addition -----
Or if it’s not a big deal because good ‘equals’ implementations will return false, still I think the documentation can be more accurate. I’m a little bit worried about that the behavior varies by expression or optimizer, not value. To me, that seems requiring too much knowledge for development.