8 minutes read

Kotlin is a language that adapts to your way of programming or code writing. Kotlin allows you to define your custom implementations of operators and allows making your code cleaner and more readable.

In this topic, you will learn how to define your own implementations of operators on your data types.

Operator overloading

Kotlin allows you to define your own operators on certain types. These operators have predefined symbolic representation, like "+" or "+=", and precedence. To define our custom operator, we must meet these requirements:

  • Provide a member function or an extension function with a specific name for the corresponding type.

  • This type becomes the left-hand side type for binary operations and the argument type for the unary ones.

To customize an operator, we should mark the function with the operator modifier.

operator fun String.unaryMinus() = this.reversed()

fun main() {
    val name = "Kotlin"
    println(-name) // niltoK
}

Also, you can combine operator with the infix modifier.

infix operator fun String.times(n: Int) = this.repeat(n)

fun main() {
    val s = "Kotlin"
    println(s * 3) //KotlinKotlinKotlin
}

Unary operations

These are functions that generally have no parameters.

Unary prefix operators

Expression

Translated to

+a

a.unaryPlus()

-a

a.unaryMinus()

!a

a.not()

Increments and decrements

Expression

Translated to

a++

a.inc()

a--

a.dec()

operator fun Pair<Int, Int>.unaryMinus() = Pair(-first, -second)
operator fun Pair<Int, Int>.inc() = Pair(first + 1, second + 1)

fun main() {
    var p = Pair(1, 2)
    println(-p) // (-1, -2)
    println(++p) // (2, 3)
}

Binary operations

These are functions that generally have two parameters.

Arithmetic operators

Expression

Translated to

a + b

a.plus(b)

a - b

a.minus(b)

a * b

a.times(b)

a / b

a.div(b)

a % b

a.rem(b)

a..b

a.rangeTo(b)

operator fun Pair<Int, Int>.plus(other: Pair<Int, Int>) = 
    Pair(first + other.first, second + other.second)


fun main() {
    var point1 = Pair(1, 2)
    var point2 = Pair(3, 4)
    println(point1 + point2) // (4, 6)
}

in operator

For in and !in, the procedure is the same, but the order of arguments is reversed.

Expression

Translated to

a in b

b.contains(a)

a !in b

!b.contains(a)

operator fun Pair<Int, Int>.contains(n: Int) = n in first..second

fun main() {
    var point1 = Pair(1, 2)
    println(1 in point1) // true
}

Indexed access operator

Square brackets [] are translated to calls to get and set.

Expression

Translated to

a[i]

a.get(i)

a[i, j]

a.get(i, j)

a[i_1, ..., i_n]

a.get(i_1, ..., i_n)

a[i] = b

a.set(i, b)

a[i, j] = b

a.set(i, j, b)

a[i_1, ..., i_n] = b

a.set(i_1, ..., i_n, b)

operator fun Pair<Int, Int>.get(n: Int) = when (n) {
    0 -> first
    1 -> second
    else -> throw IndexOutOfBoundsException()
}

fun main() {
    var point1 = Pair(1, 2)
    println(point1[0]) // 1
    println(point1[1]) // 2
}

invoke operator

Parentheses with parameters are translated to calls to invoke.

Expression

Translated to

a()

a.invoke()

a(i)

a.invoke(i)

a(i, j)

a.invoke(i, j)

a(i_1, ..., i_n)

a.invoke(i_1, ..., i_n)

operator fun Pair<Int, Int>.invoke(newLine: Boolean) {
    print("($first, $second)")
    if (newLine) println()
}

fun main() {
    var point1 = Pair(1, 2)
    point1(true) // [1, 2]


}

Augmented assignments

They perform the same operations as binary methods but assign the result to the same object.

Expression

Translated to

a += b

a.plusAssign(b)

a -= b

a.minusAssign(b)

a *= b

a.timesAssign(b)

a /= b

a.divAssign(b)

a %= b

a.remAssign(b)

operator fun StringBuilder.plusAssign(other: String) {
    this.append(other)
}

fun main() {
    val name = StringBuilder("Kotlin")
    name += " is awesome"
    println(name) // Kotlin is awesome
}

Equality and inequality operators

These operators only work with the function equals(other: Any?) implementation of a class or type.

Expression

Translated to

a == b

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

a != b

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

class Point(val x: Int, val y: Int) {
    override fun equals(other: Any?): Boolean {
        if (other is Point) {
            return other.x == x && other.y == y
        }
        return false
    }
}

fun main() {
    val p1 = Point(1, 2)
    val p2 = Point(1, 2)
    println(p1 == p2) // true
}

Comparison operators

These operators only work with the function compareTo() implementation of a class or type.

Expression

Translated to

a > b

a.compareTo(b) > 0

a < b

a.compareTo(b) < 0

a >= b

a.compareTo(b) >= 0

a <= b

a.compareTo(b) <= 0

class Point(val x: Int, val y: Int) : Comparable<Point> {
    override fun compareTo(other: Point): Int {
        return if (x == other.x) y.compareTo(other.y) else x.compareTo(other.x)
    }
}

fun main() {
    val p1 = Point(1, 2)
    val p2 = Point(1, 2)
    println(p1 < p2) // false
    println(p1 <= p2) // true
}

Conclusion

Operator overloading is a powerful tool for making our code more readable and maintainable. To use it, you must take into account the indicated requirements. It will help you take your code to another level of quality in managing your types and methods.

Now is the time to check what you have learned with some tasks. Let's go!

46 learners liked this piece of theory. 4 didn't like it. What about you?
Report a typo