So my colleague mentioned operator overloading, and how much fun it is.
So I wrote a little test.
package org.mrbear.kotlin.operators | |
import org.assertj.core.api.Assertions.assertThat | |
import org.mrbear.kotlin.AddressFactory | |
import org.mrbear.kotlin.Person | |
import org.mrbear.kotlin.PersonFactory | |
import org.testng.annotations.Test | |
class OperatorOverloadingTest { | |
@Test | |
fun testStandardInOperator() { | |
val list = listOf("A", "B", "C") | |
assertThat("A" in list).isTrue() | |
assertThat("D" in list).isFalse() | |
val str = "MRBEAR" | |
assertThat("E" in str).isTrue() | |
assertThat("H" in str).isFalse() | |
} | |
@Test | |
fun testCustomInOperator() { | |
val addressBook = | |
AddressBook(listOf(PersonFactory.alexanderGrahamnBell, PersonFactory.mrBear, PersonFactory.lordKelvin)) | |
assertThat("Kelvin" in addressBook).isTrue() | |
assertThat("Mr. Bear" in addressBook).isFalse() | |
assertThat(PersonFactory.alexanderGrahamnBell in addressBook).isTrue() | |
assertThat(PersonFactory.smith in addressBook).isFalse() | |
} | |
@Test | |
fun testCustomNotInOperator() { | |
val addressBook = | |
AddressBook(listOf(PersonFactory.alexanderGrahamnBell, PersonFactory.mrBear, PersonFactory.lordKelvin)) | |
assertThat(PersonFactory.alexanderGrahamnBell notIn addressBook).isFalse() | |
assertThat(PersonFactory.smith notIn addressBook).isTrue() | |
} | |
@Test | |
fun testCustomNotOperator() { | |
assertThat(PersonFactory.alexanderGrahamnBell.not()).isEqualTo(Person("notAlexander", "Graham", "Bell", 55, AddressFactory.addressInAmerica)) | |
assertThat(!PersonFactory.alexanderGrahamnBell).isEqualTo(Person("notAlexander", "Graham", "Bell", 55, AddressFactory.addressInAmerica)) | |
} | |
} | |
operator fun Person.not(): Person = | |
Person("not${this.firstName}", this.middleName, this.lastName, this.age, this.address) | |
infix fun Person.notIn(addressBook: AddressBook): Boolean = !(this in addressBook) | |
class AddressBook(private val listOf: List<Person>) { | |
operator fun contains(lastName: String): Boolean { | |
return listOf.map { it.lastName.uppercase() }.contains(lastName.uppercase()) | |
} | |
operator fun contains(person: Person): Boolean { | |
return listOf.contains(person) | |
} | |
} |
It works pretty good, but for infix functions it has a very high "syntactic sugar" and less about "real" operator overloading.
Also, the reference page in [1] indicates that for Infix functions, the precedence has fixed rules that do not (always) conform to precedence that we would assume.
My colleague told me that a number of infix functions were created for QueryDSL, so we could write a semblance of SQL in Kotlin, but the precedence tends to screw things up.
We removed these infix functions from our source code again.
So, use sparingly and only when it makes sense. For example if it makes your code a magnitude easier to read/reason about and preferable with as small a scope as possible.
References
- [1] KotlinLang - Functions
- https://kotlinlang.org/docs/functions.html#function-scope
- Baeldung - Operator Overloading in Kotlin
- https://www.baeldung.com/kotlin/operator-overloading
- Baeldung - Infix Functions in Kotlin
- https://www.baeldung.com/kotlin/infix-functions