Skip to content
Table of contents
Operator Overloading

Operator Overloading

1 min read #kotlin / #advanced

Back in the school while I was learning C++, operator overloading was one of the topics in academic. But in practice, I rarely overload operators. However, in Kotlin, we do use the overload operators, without noticing.

val list: List = listOf('a', 'b', 'c', 'd', 'e', 'f')
print(list[0]) //a
print(list.get(2)) //c
print('d' in list) //true
print(list.contains('e')) //true
kotlin

Accessing list elements using square bracket calls the overloaded operators get() and set(), while in calls contains().

What is operator overloading

In programming, overloading means adding extra meaning to something that is already exists.

For example, operator overloading allows us to use + to do something meaningful for our custom data type. This concept was popular back in C++ days, but because C++ had no garbage collection, writing overloaded operators were difficult. Java deemed operator overloading as “bad” and didn’t allow it in Java. Python language demonstrated the simplicity of operator overloading with the support of garbage collection. It constraints us to a limited set of operators, so as C++ did. Scala experimented with allowing to invent own operators, but the feature was abused by some programmers and created incomprehensible code. Kotlin simplified the process, but restricts the choice to a reasonable and familiar set of operators.

How it looks

In order to overload operator, Kotlin uses the keyword operator.

data class Num(val n: Int)

operator fun Num.plus(rhs: Num) =
  Num(n + rhs.n)

Num(4) + Num(4) // Num(8)
Num(4).plus(Num(2)) // Num(6)
kotlin

What if we want to make n as private in the data class Num? In this case, the extension function could not access to n for rhs. We could consider defining an operator as member function then it will be fine.

data class Car(private val wheels: Int) {
  operator fun plus(anotherCar: Car) =
    Car(wheels + anotherCar.wheel)
}

Car(4) + Car(6) // Car(10)
kotlin

Equality

Invoking == (equality) or != (inequality) calls the equals() member function. data class automatically redefine equals() to compare the stored data, but if we don’t redefine equals() for non-data classes, the default version compares reference rather than contents.

Arithmetic operators

Let’s try arithmetic operators (eg. +, -) as extension to class E.

Invoke

Placing parentheses after an object generates a call to invoke(). The invoke() operator makes an object look like a function.

class Cost {
  operator fun invoke() = "invoke()"
  operator fun invoke(i: Int) = "invoke($i)"
  operator fun invoke(i: Int, j: String) = "invoke($i, $j)"
  operator fun invoke(i: Int, j: String, k: Double) = "invoke($i, $j, $k)"
}

val cost = Cost()
cost() //"invoke()"
cost(1) //"invoke(1)
cost(2, "hey") //invoke(2, hey)
cost(3, "you", 1.5) //invoke(3, you, 1.5)
kotlin

Conclusion

Operator overloading is not an essential feature, but is an excellent example of how a language is more than just a way to manipulate the underlying computer. With overloading, we have been able to express the abstractions, so other humans have an easier time to understand the code without getting bogged down in needless details.