You already know that objects are really complex structures and variables only point to objects. This time, you will learn about equality and how to understand that variables point to the same object. In addition, you will finally fully understand the meaning of the
val keyword and avoid one of the most common beginner mistakes: assuming that the
val keyword guarantees immutability.
Imagine a situation: you receive two identical messages from your friend. The messages are “Hi” and “Hi”. You see them and understand: the messages are the same. If you want to compare these messages in Kotlin, you can store them as string values:
val msg1 = "Hi" val msg2 = "Hi"
Then you can use the comparison operator
==. For example,
print(msg1 == msg2) gives
print(msg1 == "Hello") gives
msg2 have the same state, which is called structural equality. Also, you may check for inequality using the operator
!= . For example,
print(msg1 != "Hello") gives
Note that some complex data types may not have the operator
==. We will discuss this later.
Box in the following examples has this operation
Let’s look at an example of copying a mutable object. Suppose you have a box that stores balls, and you can add one ball to it. Try to copy this box and change the original:
val blueBox = Box(3) // box with 3 balls val azureBox = blueBox println(blueBox == azureBox ) // true, it's a copy blueBox.addBall() // add a ball to the first box println(blueBox == azureBox ) // true, the second box also contains 4 balls
When you change the first box, its copy changes, too. This is because
azureBoxpoint to the same object. How do you check this? Let’s see how to check the referential equality.
You know that variables can have the same state and can be the same (point to the same object). In both cases,
true. However, Kotlin provides a special operator
=== to check if the variables point to the same object. For example:
val blueBox = Box(3) val azureBox = blueBox val cyanBox = Box(3) println(blueBox == azureBox) // true println(blueBox === azureBox) // true, azureBox points to the same object println(blueBox == cyanBox) // true println(blueBox === cyanBox) // false, cyanBox points to another object
cyanBox have the same state, but they point to different objects. In this case, if you change the state of
cyanBox remains the same:
blueBox.addBall() println(blueBox == cyanBox) // false
Also, you may check for referential inequality with the operator
!== . For example,
print(blueBox !== cyanBox) gives
Another interesting thing about the
=== operator is the equality of immutable objects. Let’s look at the following example:
var two = 2 var anotherTwo = 2 println(two === anotherTwo) // true
These variables point to the same object! Don’t worry about this: as you remember, you cannot change an immutable object, so if you try to do something with the variable, it will point to a new object and other variables will still point to the old object. Try to change
two++ println(two) // 3 println(anotherTwo) // 2
So, immutable objects are really useful and help you avoid a lot of possible problems with copying.
Base types and equality
You are already quite familiar with objects in Kotlin: you have worked with text and number data a lot. In many programming languages, primitive data types – or primitives – store the most often used simple data types. Their internal structure is organized in its own way. This is not the case in Kotlin. As you might have guessed, the familiar
Double in Kotlin are also objects! But there is a nuance. For example, the
String variables behave just like the primitive types of data in other programming languages, but at the same time, they are objects – in other words, immutable objects. Let’s look how it works:
var a: Int = 100 val anotherA: Int = a println(a == anotherA) // true println(a === anotherA) // true a = 200 println(a == anotherA) // false println(a === anotherA) // false
As you can see, when we change the value of the variable
a = 200, we do not change its object – the variable
a is assigned a new reference to the object with the value
Let’s look at one example, but now with the
Double type of data:
var d1: Double = 1.5 val d2 = d1 println(d1 === d2) // true d1 += 1 // d1 is 2.5 now println(d1 === d2) // false
As you can see, the result is the same. Now, let’s look at an example with modifiable objects:
val list1: MutableList<Int> = mutableListOf() val list2: MutableList<Int> = list1 list1.add(1) println("list1 $list1") // list1  println("list2 $list2") // list2  list2.add(5) println("list1 $list1") // list1 [1, 5] println("list2 $list2") // list2 [1, 5]
As you can see in this example, the variables
list2 refer to the same object. When you change the object through the first variable, we see the updated data through the second variable.
Let’s go over the main points of the topic once again:
- Structural equality of variables implies equality of inner states.
- You can use the operators
!=to check structural equality.
- Referential equality of variables means that these variables point to the same object.
- You can use the operators
!==to check referential equality.
valkeyword means that you cannot reassign the variable, not immutability.