Equality and Operator Overloading

This section documents Kotlin 2.4.x on the JVM, as published at kotlinlang.org, which is the reference these pages are written and verified against.

This content was generated with the assistance of AI and should be verified against kotlinlang.org before being relied on in production.

This section’s bibliography lists the reference material consulted while preparing these pages.

Kotlin splits Java’s single == into two distinct operators, and generalizes every arithmetic/comparison operator into an overloadable, ordinary function call.

Structural (==) vs. Referential (===) Equality

  • == calls .equals() (with a null-safe check built in) — structural equality, comparing content. This is the Kotlin equivalent of what Java code has to remember to write as Objects.equals(a, b) or a.equals(b).

  • === compares referential equality — whether both sides are literally the same object instance. This is what Java’s == does for reference types.

val a = Point(1, 2)          // a data class -- see Data Classes and Destructuring
val b = Point(1, 2)
val c = a

println(a == b)    // true  -- same content (structural), calls the generated equals()
println(a === b)   // false -- different instances
println(a === c)   // true  -- the exact same instance

This split removes the single most common Java equality bug — accidentally comparing two boxed/String objects with == and getting a reference comparison instead of the intended content comparison.

this Expressions

this refers to the current receiver, exactly as in Java, with one addition: inside a class with several nested scopes (a lambda, an inner class), a labeled this@Label disambiguates which enclosing receiver is meant:

class Outer {
    val name = "outer"

    inner class Inner {
        val name = "inner"

        fun show() {
            println(this.name)        // "inner" -- the Inner instance itself
            println(this@Outer.name)   // "outer" -- explicitly the enclosing Outer instance
        }
    }
}

Overloadable Operator Conventions

Every operator symbol in Kotlin — arithmetic, comparison, indexing, function-call syntax, iteration — is, under the hood, a call to a specifically-named function that any type can provide by marking it operator. Providing that function is what makes the symbol usable on your own type:

Symbol Function name Example

a + b, a - b, a * b, a / b, a % b

plus, minus, times, div, rem

operator fun plus(other: Vector) = Vector(x + other.x, y + other.y)

a[i], a[i] = v

get, set

operator fun get(index: Int) = items[index]

a(), a(x, y)

invoke

operator fun invoke(x: Int) = x * factor — lets an instance be called like a function

a < b, a ⇐ b, a > b, a >= b

compareTo

operator fun compareTo(other: Money) = amount.compareTo(other.amount)

for (x in a)

iterator

operator fun iterator(): Iterator<Item> = items.iterator()

a..b

rangeTo

the function behind the range operator — see Operators and Ranges

a in b

contains

operator fun contains(item: Item) = items.any { it == item }

data class Vector(val x: Int, val y: Int) {
    operator fun plus(other: Vector) = Vector(x + other.x, y + other.y)
}

class Multiplier(val factor: Int) {
    operator fun invoke(value: Int) = value * factor   // an instance is now callable like a function
}

val v = Vector(1, 2) + Vector(3, 4)     // Vector(x=4, y=6) -- "+" resolves to plus()

val doubler = Multiplier(2)
println(doubler(21))                     // 42 -- "doubler(21)" calls invoke(21)

Operator overloading is intentionally not extensible beyond this fixed set of named conventions — unlike C++, Kotlin has no way to invent an entirely new operator symbol, only to give an existing symbol meaning for a new type by implementing its corresponding function.

See Also