21

What is the difference between the comparison operators == and === in Kotlin?

class A {
  var foo = 1
}
    
var a1 = A()
var a2 = A()
    
println(a1 == a2)  // output false
println(a1 === a2) // output false
    
a1 = a2 
       
println(a1 == a2)  // output true
println(a1 === a2) // output true
lmiguelvargasf
  • 63,191
  • 45
  • 217
  • 228
SergeyBukarev
  • 1,478
  • 1
  • 12
  • 19

4 Answers4

24

Briefly speaking, from the docs:

In Kotlin there are two types of equality:

  • Structural equality (a check for equals()) => ==
  • Referential equality (two references point to the same object) => ===

Detailed answer:

Structural Equality (==)

The negated counterpart of == is !=

By convention, an expression like a == b is translated to:

a?.equals(b) ?: (b === null)

if a is not null, it calls the equals(Any?) function, otherwise it checks that b is referentially equal to null.

To provide a custom equals check implementation, override the equals(other: Any?): Boolean function. Functions with the same name and other signatures, like equals(other: Foo) , don't affect equality checks with the operators == and !=.

Referential Equality (===)

The negated counterpart of === is !==

a === b evaluates to true if and only if a and b point to the same object. For values which are represented as primitive types at runtime (for example, Int ), the === equality check is equivalent to the == check.

Code explanation

Let's assume the definition of A is as you have defined in your question.

Snippet 1

>>> var a1 = A()
>>> var a2 = A()
>>> a1 == a2 // a1 and a2 are different instances of A
false
>>> a1 == a1
true
>>> a2 == a2
true
>>> a1 === a2 // a1 and a2 have references to different objects
false

For regular classes, the implementation of equals is inherited from Any, and just make the object equal to itself.

Snippet 2

>>> var a1 = A()
>>> var a2 = A()
>>> a1 = a2 
>>> a1 == a2
true
>>> a1 === a2
true

a1 and a2 point to the same object that is why a1 == a2 and a1 === a2 return true.

Snippet 3

Let's override equals(Any?) of A as follows:

class A {
    var foo = 1
    override fun equals(other: Any?): Boolean {
        if (other == null || other !is A)
            return false
        return foo == (other as A).foo
    }
}

Now let's run the following:

>>> var a1 = A()
>>> var a2 = A()
>>> a1 == a2
true
>>> a1 === a2
false

Notice that a1 and a2 are structurally equal even though they reference difference objects.

lmiguelvargasf
  • 63,191
  • 45
  • 217
  • 228
  • Misleading answer! In snippet 2, a1 === a2 should be false. – Sriyank Siddhartha Feb 26 '22 at 02:41
  • @SriyankSiddhartha No that will be true, as a1 = a3 written above, so now points(reference) to same memory location – Shirish Herwade May 12 '22 at 19:43
  • I thought the same thing as @SriyankSiddhartha at first. Snippet 2 would be less confusing if it said `var a2 = a1` instead of `var a2 = A(); a1 = a2`, where the last statement is easy to overlook as it resembles the equality tests. – LarsH Jan 05 '23 at 16:32
  • Regarding "For regular classes, the implementation of equals is inherited from Any, and just make the object equal to itself," it would be helpful to note that String overrides `equals()` to provide a proper content equality check, but Array doesn't (surprise!). – LarsH Jan 05 '23 at 16:40
14

In Kotlin, two types of equality are available. These are: Structural Equality & Referential Equality.

class A {
  var foo = 1
}

var a1 = A()
var a2 = A()

Here a1 and a2 are two instances of class A.

println(a1 == a2)

It prints false because a1 and a2 are not structurally equal.

println(a1 === a2)

It prints false because a1 and a2 are not referencing the same object.

But, if you execute this line: a1 = a2 then,

a1 and a2 will be structurally equal and a1 is referencing to the a2 instance. That's why,

println(a1 == a2)
println(a1 === a2)

both these lines returns true.

Avijit Karmakar
  • 8,890
  • 6
  • 44
  • 59
3

Structural Equality means content should be same

"checks for equals() or ==".

Referential Equality means both instances should be pointed to same pointer.

"two objects points to same reference or ==="

You can override equals() and do the stuff as below.

class Person(val p : String) {

  override fun toString(): String = "Person(p$p)"
    override fun equals(other: Any?): Boolean {
        if(other == null || other !is Person) {
           return false
        }
       return p == other.p
    }
}

lets test this.

var a = Person("abc")
var b = Person("abc")

Line 1 => println(a == b)
Line 2 => println(a === b)

Line1 prints "true"

Line2 prints "false"

a = b

Line1 prints "true"

Line2 prints "true"

Finally,

=== always checks for reference equality while == checks for equivalent content and is based on your implementation.

GvSharma
  • 2,632
  • 1
  • 24
  • 30
1

Kotlin == and === operators (fresh look : 6 cases)

In Kotlin if a and b represent two variables then println(a==b) checks whether the two values are structurally equal or not. But println(a===b) checks whether a and b are having the same reference or not. Following examples would make this clear.

// Case 1 : a and b are both Int
// EqCase1.kt
fun main() {
    var a = 5
    var b = 7
    println(a==b) // false
    //println(a===b) // identity equality for Int is deprecated
    a = b
    println(a==b) // true
    //println(a===b) // identity equality for Int is deprecated
}


// Case 2 : a and b are Double and Integer
// EqCase2.kt
fun main() {
    var a = 5.0
    var b = 7
    //println(a==b) //  operator '==' cannot be applied to 'Double' and 'Int'
    //println(a===b) // operator '===' cannot be applied to 'Double' and 'Int'
    //a = b // inferred type is Int but Double was expected
    //println(a==b) //  inferred type is Int but Double was expected
    println(a===b) // identity equality for Int is deprecated
}


// case 3 : a and b are mathematically same but of different types (Double and Any(say Int))
// EqCase3.kt
fun Eq(a: Double,b: Any) =  if (a == b) "(a == b) is true" else "(a == b) is false"
//fun Eq1(a: Double,b: Any) =  if (a === b) "(a === b) is true" else "(a === b) is false"
fun main() {
    println(Eq(5.0,5)) // (a == b) is false
    //println(Eq1(5.0,5)) 
}

/* O/p
EqCase3.kt:5:34: warning: identity equality for arguments of types Double and Any can be unstable because of implicit boxing
fun Eq1(a: Double,b: Any) =  if (a === b) "(a === b) is true" else "(a === b) is false"
*/


// Case 4 : a and b are both Booleans
// EqCase4.kt
fun main() {
    var a = true
    var b = false
    println(a==b) // false
    //println(a===b) // identity equality for arguments of Boolean is deprecated
    a = b
    println(a==b) // true
    //println(a===b) // identity equality for arguments of Boolean is deprecated
}


// Case 5 : a and b are both Strings
// EqCase5.kt
fun main() {
    var a = "Anil"
    var b = "Vijay"
    println(a==b) // false
    println(a===b) // false
    a = b
    println(a==b) // true
    println(a===b) // true
}


// Case 6 : a and b are both of type arrayOf
// EqCase6.kt
fun main() {
    var a = arrayOf("A","B","C")
    var b = arrayOf("a","b","c")
    println(a==b) // false
    println(a===b) // false
    a = b
    println(a==b) // true
    println(a===b)  // true
}