Kotlin Operator Overloading
Another day, another article!
This time you’ll discover how the Operator Overload works in Kotlin.
By overloading operators, you can create a more semantic code that will be easier to read and maintain.
In Kotlin, as in every language, we have predefined operators to perform certain operations.
The most typical are the addition (+), subtraction (-), multiplication (*) or division (/index.html), but there are a few more.
In some languages, such as Java, these operators are limited to certain types of data, and we have no way to make other types use them.
There are other languages like Scala in which we can invent any type of operator that occurs to us, since the names of the functions accept any symbol.
An intermediate solution is taken in Kotlin: there are a number of predefined operators, but we can overload them for any kind of data.
Kotlin Operator Overloading
As we talked, Kotlin can overload a number of operators, implementing the corresponding function in our class. This function must be marked with the reserved word operator
.
The operators are basically the following:
Unary Operators
+a | a.unaryPlus() |
-a | a.unaryMinus() |
!a | a.not() |
a++ | a.inc() |
a– | a.dec() |
Binary Operators
a + b | a.plus(b) |
a – b | a.minus(b) |
a * b | a.times(b) |
a / b | a.div(b) |
a % b | a.mod(b) |
a..b | a.rangeTo(b) |
a in b | b.contains(a) |
a !in b | !b.contains(a) |
a += b | a.plusAssign(b) |
a -= b | a.minusAssign(b) |
a *= b | a.timesAssign(b) |
a /= b | a.divAssign(b) |
a %= b | a.modAssign(b) |
Array type operators
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) |
Equals Operation
a == b | a?.equals(b) ?: b === null |
a != b | !(a?.equals(b) ?: b === null) |
The equals
operations are a bit different, because they use a more complex translation to do a correct check, and because they expect an exact specification of the function and not just a specific name for it. The function must be implemented exactly like this:
1 |
fun equals(other: Any?): Boolean |
Invoking Functions
a(i) | a.invoke(i) |
a(i, j) | a.invoke(i, j) |
a(i_1, …, i_n) | a.invoke(i_1, …, i_n) |
An example
Imagine that you have a data model for companies, each of which has a list of employees.
You could use the get
operator to access the positions using brackets. The implementation is very easy:
1 2 3 4 5 |
class Employee(val id: Long, val name: String) class Company(private val employees: List) { operator fun get(pos: Int) = employees[pos] } |
And that’s how you could use it:
1 2 |
val company = Company(listOf(Employee(1235, "John"), Employee(2584, "Mike"))) val mike = company[1] |
But you could go beyond, and use the id to recover the value, implementing the function like this:
1 2 3 |
operator fun get(id: Long) = employees.first { it.id == id } val mike = company[2584] |
Conclusion
Operators can help us to improve readability by using common symbols for known operations.
Be careful not to use them in situations where they can lead to confusion to readers, and they can become a really interesting tool.
Happy Coding!