Sunday 1 August 2021

Android Java to Kotlin migration by example part 13: operator overloading

This time again we're going to be talking about something available in Kotlin, but not in Java: operator overloading.

Operator overloading means that we can apply operators, like + (someobject+anotherone) or [ ] (someobject[5]) to our objects. Expression like this (someobject + anotherone) can be thought of as methods: it's just another way of expressing someobject.plus(anotherone)someobject[5] would mean "call the method [] with parameter 5 on someobject.  

Altough operator overloading is an old concept (it's common in C++, where - for example - the famous cout<<"Something"; actually uses overloaded left bit shift operator for object cout of class ostream; it's also available in C# and Python), it's not implemented in Java (which was a deliberate choice of its creators).

Still, it can be very useful and Kotlin allows it.

Let's get to our code. I think the most basic example of operator overloading is use of class representing complex numbers (the code below is the full program, with both class definition and it's use):

package com.mypackage

import kotlin.Exception
import kotlin.math.sqrt

class Complex(var re: Float, var im: Float){

fun modulus(): Float{ // just a method
return sqrt(re*re+im*im)
}

// "this" can be omitted
operator fun plus(another: Complex): Complex{ // a+b
return Complex(this.re+another.re,this.im+another.im)
}

operator fun minus(another: Complex): Complex{ // a - b
return Complex(re-another.re, im-another.im)
}

operator fun times(another: Complex): Complex{
var resultRe = re*another.re-im*another.im
var resultIm = re*another.im+im*another.re
return Complex(resultRe, resultIm)
}

operator fun compareTo(another: Complex): Int{ // == > < <= >=
if(this.modulus()<another.modulus())
return -1
if(this.modulus()>another.modulus())
return 1
return 0
}

operator fun get(arg: Int): Float{ // someComplex[0]
if(arg==0)
return this.re
if(arg==1)
return this.im
throw IllegalArgumentException()

}

override fun equals(other: Any?): Boolean {
if(other is Complex)
return (this.re==other.re && this.im == other.im)
else
throw Exception("wrong class")
}

override fun toString(): String{
return "($re,$im)"
}

}

fun main() {
val c = Complex(1.0f,0.0f)
val c2 = Complex(1.0f, 0.0f)
val c3 = Complex(10.0f,10.0f)
val sum = c+c2

val sum2 = c.plus(c2)

val someMul = Complex(10.0f,5.0f)* Complex(7.0f,-2.0f)

println("multiplied:$someMul")

println(sum)
println(sum2)
println(sum[0])
println(c3>c2)

println(c==c2)
}

... which defines the class Complex representing a complex number, and overloads the infix "+", "-", "*", comparison (<, >, >=, <=) and [ ] operators. There is also modulus function, which is just a method... And which we use to define our overloaded comparison operators behaviour (operator fun compareTo).

The execution of the code above results in the following output:

multiplied:(80.0,15.0)
(2.0,0.0)
(2.0,0.0)
2.0
true
true

We can see that my implementation of complex multiplication is correct ;)

The +, -, * operators, implemented as plus,  minus, and times are quite self-explanatory: they take one parameter, and use this.re and this.im (or just re and im) of current (left to operand) values to compute the results, which they return. 

In order to override them, we use operator keyword before a method definition. Please note they can be called both as operators ( val sum = c+c2 ) or just as methods ( val sum2 = c.plus(c2)).

In comparison operator overload (operator fun compareTo(another: Complex)) I've used previously defined modulus method, because there is no unequivocal way of comparing complex numbers - so I compare their moduli. This operator should return -1 if the first operand is "smaller" than second one (in our case it's modulus is smaller), 1 if it's "bigger" and and 0 it they're the same.

I've also overloaded get operator, which affects suqare brackets, like in the call println(sum[0]). What it does, is simply return real part of our complex as 0-th element and the imaginary part as 1-st one. If called with something else than 0 or 1, it would throw IllegalArgumentException(). Typically this operator would be used for some class representing collection (like list - please note in Java you'd have to use somelist.get(i) instead of just somelist[i] to get i-th element of the somelist List or like text string, where it would return n-th character).

There are other operators to overload, like not (!) - but I simply can think of no use of it regarding complex numbers :). When you type operator fun  (with the spacebar after the word fun) and hit Ctrl+Space in IntelliJ Idea or Android Studio, you'll get the full list of the operators possible to overload with their symbols on the right and corresponding keywords (like plus or minus in the example above) on the left:



Please note compareTo does not affect == comparison effect: that's what overriden function override fun equals(other: Any?) does.

A word on Java - of course, lack of operator overloading in Java doesn't mean we could not implement a class Complex in this language. We can, of course. But the only way of representing "sum of complex1 and complex2" would be some function or method, like complex1.add(complex2) or maybe sum(complex1, complex2) - not the intuitive and obvious Kotlin's complex1+complex2.

Well, that would be basics. More details, including the full list of operators available for overloading in Kotlin are available in the Kotlin's documentation.


No comments:

Post a Comment

Python crash course part 10: inheritance and polymorphism

In the last part we've shown how to create and use a class in Python. Today we're going to talk about inheritance: wchich means cre...