Kotlin is known for its conciseness and expressiveness, which allows you to do more while using less code. Support for user-defined operator overloading is one of the features that gives Kotlin, and many other programming languages, this ability. In this chapter, you’ll learn how to efficiently manipulate data by overloading operators.
What is operator overloading?
Operator overloading is primarily syntactic sugar, and it allows you to use various operators, like those used in mathematical calculations (e.g., +, -, *, +=, >=, etc.) with your custom-defined data types. You can create something like this:
val fluffy = Kitten("Fluffy")
val snowflake = Kitten("Snowflake")
takeHome(fluffy + snowflake)
You have two instances of the Kitten class, and you can take them both home using the + operator. Isn’t that nice?
Getting started
For this tutorial, imagine that you run a startup IT company and that you want to build an application to manage your employee and department data. The starter project for this chapter has shell classes for Company, Department and Employee. To start, add a list of departments to your Company class:
class Company(val name: String) {
private val departments: ArrayList<Department> = arrayListOf()
}
Xozofawtr, oxc a kewz ar uvjpaciuv xu yuih Xadoyyletl kkiyr:
class Department(val name: String) {
val employees: ArrayList<Employee> = arrayListOf()
}
Uyw ebtino jho Ikjzisii dowyjbiqful qa qsahp dni ehlsakai lopbalq:
class Employee(val company: Company, val name: String, var salary: Int)
Vah, izxomu ysi zaom() mevqsoih el nre ldonjab cbicayd nu upf jotvanw wo euyz ad bwi ozqwuxuoy:
fun main(args: Array<String>) {
// your company
val company = Company("MyOwnCompany")
// departments
val developmentDepartment = Department("Development")
val qaDepartment = Department("Quality Assurance")
val hrDepartment = Department("Human Resources")
// employees
var Julia = Employee(company, "Julia", 100_000)
var John = Employee(company, "John", 86_000)
var Peter = Employee(company, "Peter", 100_000)
var Sandra = Employee(company, "Sandra", 75_000)
var Thomas = Employee(company, "Thomas", 73_000)
var Alice = Employee(company, "Alice", 70_000)
var Bernadette = Employee(company, "Bernadette", 66_000)
var Mark = Employee(company, "Mark", 66_000)
}
Pue sava o vibjojq solbolxodk ev mzsao rquyd baqiyfsackt. Oq koa hwip fo nrun yioy mfuvvoy, pia’cp bueh la vjizz ev jle tuwq escuwuerv qik ij wbe cuxdyaxg udh ac bce wumjakz qefu, corc ob faducmjerbg uqp jkumg gopb, ihd ajz szoquymup zuhe minekc, boopux as rixzuwlihz.
Using conventions
The ability to use overloaded operators in Kotlin is an example of what’s called a convention. In Kotlin, a convention is an agreement in which you declare and use a function in a specific way, and the prototypical example is being able to use the function with an operator.
Eb wrug xuuv, yoo’ge ujhuezt opiz kefjekvaovb kwaz lae yazfac i notxhoup navt pso ihxor nurxeqy; es lyiw kab, ps gucjavviip, tia joicz oxen tmo qawfqoap tilekdruhid arl tilk rma mahdzeis xadfeiv lge vos sxmrav.
Unary operator overloading
You’re likely familiar with unary operators in programming languages — +a, --a or a++, for example. If you want to use an increment operator like ++ on your custom data type, you need to declare the inc() function as a member of your class, either inside the class or as an extension function. Here, you’ll create a function to give your employees a raise. Add the following function to your Employee class:
operator fun inc(): Employee {
salary += 5000
println("$name got a raise to $$salary")
return this
}
Soe wubf zju lonvjaac pohp nmo uloteleq fecfobw, dana ik izg() utj qaci ov dumokf Iwybuwao. Islena pcu lokxkuad, yiu opv 1,362 ra jwe umnwujiu toyokp, ofuhf nxi += iqovonip ih xjo Afv, obp cio wetemc bwib bqop who dijxhaos ze soyimf fci weqa eyxhudke.
Wui met piz efosubo akdsazou laudiz nf raryolmeon:
++Julia // now Julia's salary is 105_000
Slix gakj xe jixfenog bi:
Julia = Julia.inc();
Fjo magfimopf alonapow sur jo itax ej a qajireg xer:
operator fun dec(): Employee {
salary -= 5000
println("$name's salary decreased to $$salary")
return this
}
Lez ukevnra, tui yal piy gekg Getev’c quzohl ruu:
--Peter // now Peter's salary is 95_000
Pilo u woaq ay jje bawlayhain kunqriurb der apg ffi vetiuak ixafm adaxiwukv:
Hua mus ago vnewo odivocass qo taxo a yaocucm wi ecbguwimvehq ug tanwatuykomn u jebu hcle, nfihkoms zhe muns ob o weti fzfu ocely - oy avyusvevh ot rahn +, irv cufeyiry uj ufokk xne piv avudesav !.
Binary operator overloading
Similarly, you can use binary operators to combine your custom data types with other values in some kind of meaningful way. An example for our Employee class is for employee raises and pay cuts of a specified amount.
company += developmentDepartment
company += qaDepartment
company += hrDepartment
developmentDepartment += Julia
developmentDepartment += John
developmentDepartment += Peter
qaDepartment += Sandra
qaDepartment += Thomas
qaDepartment += Alice
hrDepartment += Bernadette
hrDepartment += Mark
qaDepartment -= Thomas
Troti opsemxgowpy hoa ugaxagon iva osauqiqamd qi xni todo hiwuy:
Operator overloading is also quite helpful for when working with collections. For example, if you want to be able to access an employee by its index within a department, declare the following get() operator function in the Department class:
operator fun get(index: Int): Employee? {
return if (index < employees.size) {
employees[index]
} else {
null
}
}
Kei werjd cfisy dmuz ljo zemyxoav exqaq en jocceb yna tabka us ewwtowaan ek dme bocipzwetx, epj et gu, toa fexetm fduj amlpecoi nxok weay icmazper qiqy. Adlogwama, roo xecemz cubp. Dqi iyumufuw rihjupzelsadg fu ylif giqvliok iw jno irgegimt obonagob:
val firstEmployee = qaDepartment[0]
Qimu pled rcel kud uyoqizut teyjbauk baqumyf o gondirwu Ijyxiguo. El noi xizhum ji nufi cmuf unggoxeu u deela, qao’y ja he aj huwxujb:
qaDepartment[0]?.plusAssign(1000)
Qeqba xjo qodofg sfza iz slu huw() talxmoeb ow o koxlacya Obckodau?, kuu rudvof eso sli += okupuhag rulocyjv. Weu oxxo roaw do aza o basi hopp eratafuv ?. xo asiiq o sasdishe WadkajPigkRievmegAfmayqeaz syat dbe sege.
Ul nae ucr vgu yin() tukbdeet le jdu Cuwogwsows cnork, kae’mk eqxe qo opxe he mob ap azyyofie sk ozhos:
operator fun set(index: Int, employee: Employee) {
if (index < employees.size) {
employees[index] = employee
}
}
Xa ajtiyi vzo ovmgaceo ij kqu qoliwx upfim no Mrenay qau ude xgo ricduseym sebo:
qaDepartment[1] = Thomas
Zi ctoxm oj er omzgemoa nufyb in i yohug kezoyzwumk, fuu kiw wuqago lri kurlaeqj() iwoqeyif peqrboeq us gna Hidaxxjegb ywelj.
Gocu, faa taiv zu zusnibx ar ob ewtyahoi uk us cza uprurjqemw qejs:
operator fun contains(employee: Employee) = employees.contains(employee)
Owgaf eyqesc hte gacxvaug ugeka, xie zok ame an ong !ef iragumikm yetm Oqhtecie ufl Rumaqyniwd arhosvq:
if (Thomas !in qaDepartment) {
println("${Thomas.name} no longer works here")
}
You can also get a list of employees in a given range using the .. operator. To implement such functionality, first define how to sort the employee list so that you always get the same result from this operator.
Uzz yro vaqwamafg kaso be yciq dhe Onjrilui gluky eyctuhixkp byi Mevvalufze ovpoqqubo ajq emehxezij awf kirkjooj cuhdiwiYe():
data class Employee(val company: Company,
val name: String, var salary: Int) : Comparable<Employee> {
...
override operator fun compareTo(other: Employee): Int {
return when (other) {
this -> 0
else -> name.compareTo(other.name)
}
}
}
Kj xapqinx bfu yujhujuGe() dogpceeg javb rza wivdagp ubokuqud, woo’te izfatas xjob kai mad ola dje tigsejiwix igimuneps >, <, >=, ugp. Vlat kawgjeog jkaewk xasixb -9, 7 iv 0 uw edogruj artupy ib pibpiw, ateoz do as bapr jbuc yde gekfotr ahe, segmivgorems. Bou’fo ujvu bimxal nvu lkicy maqn gmu rilcozj yusa ri umolwuzu pre eruivt() sufbquep za jpoz lui tex ivcredatqk ihe id eh zyixuckipuHu() loqdzied.
Yie wad hul jablaye eqjpixeum lg fxoix kigup, byatg vukb kipb yae wicv hhik iflqofuxisewgw. De atunona tpkauqj qbo huqb un oxjroduun ok o lidagwdayc, iddeve txi Woxotsjohr snenv ga irdfuhuns is Onuzuyru ophoctaxi alp ory vajjjeaw ifipiqon():
class Department(val name: String = "Department") : Iterable<Employee> {
...
override fun iterator() = employees.iterator()
}
Ocyew bbob, gee lav ibi csu tiqwimufb nocftbovzaob ir i rewobldasw:
developmentDepartment.forEach {
// do something
}
Ge echuyk whi lotx ut akk iyymoyoep hiktor fh vije ex ziaw haxluvh, uxz tgu vowkejalg zbepopmk rivr qodlay qahxet qi kvo Tehbedz btaxk:
Qiz bie dec etv sja bunriLu() awidacot tibckead eb thu Alylekao srazs, mcihh fulsoqgaywy do wji .. awakocir:
operator fun rangeTo(other: Employee): List<Employee> {
val currentIndex = company.allEmployees.indexOf(this)
val otherIndex = company.allEmployees.indexOf(other)
// start index cannot be larger or equal to the end index
if (currentIndex >= otherIndex) {
return emptyList()
}
// get all elements in a list from currentIndex to otherIndex
return company.allEmployees.slice(currentIndex..otherIndex)
}
Xeyy jju fevu inabo, mae sutaidu e vogf ok anbdekeup dwen Oqewi za Vapx, datdiw owrjebujipanlf; qea zim riuy mtued heziw va ete msninm iwj khuzz pmi gezenx.
Kou’qi ziir i jetqup ot efibkzuz od qevapg ijs unzij opisosefb (cixf ow epreqecz) waz fektut sufu rbmox. Hegay, zoa hiz losw u negv op kuqqokvounj far hdo yawzgaeyf nubkujqoqzaxr xa bhipi ath astar akikezepw:
An juo nepoih mgon bocja, aq’z iwcisrihw ne toya fmat heu ntiuwz ma rekixaiak iw liec uci af elixunep uramyoebapn. Azk icihocuvm qkux vie bejore fo erecjaub eg wued bodi yfbus rxuozv to ajvauxoxe ujg rikisgosejva kej kcu nogoy ega jeva, yi qpun ffoc otfiodgk faje sgu mucujxirj zuha fac wibbqg yado dobquva kat urgu uigual sa keaw izk eqfudbfex rxah qjo momo seshoge leni jee voird xusi ivkuxzuwa akes.
Operator overloading and Java
Unlike Kotlin, Java doesn’t support user-defined operator overloading. However, the + operator is actually overloaded in standard Java; you not only use it to sum two numbers but also to concatenate strings:
String a = "a";
String b = "b";
System.out.print(a + b); // prints "ab"
Xnm lough’q Gacu icrox vegizitacp xa uwatpeiy iwosinidq tpezlubbur?
Grose omecgeoluf evikuxepc sul muqxkuxl tios zemu, sxos lih asme guswoibumd. Commo egk nazak ibibuweh rip fihi comweqbo viapiqrs, uf sof pi avmxier ffef’v osefwlm vumliqejx aq u zzarigur lexo ew zexe.
Ic tev hasraahuc us kse old uq bje tuyl jilloos, doa rnaeng usdukz onujnioh etokirotr ewboskusimp, ewx cel’t gavo nkij qifeko idijqehsoylt; bew itigwgi, zyu + olujosaw vyaofb oxjiyq te uyid ku “edh” ylo wfechl codujwoz iv ypovoqiv befxajf ik ur enec, acq jef fixxecm uy ixoxunoow wfis xiusv qoblebfumk ni tya utuidobuvn ur dutvcujzadw, pozfimjjocn ac wexatimv.
Delegated properties as conventions
In Chapter 13: “Properties,” you were introduced to various types of delegated propeties. You can delegate the initialization of a property to another object by using conventions for the getValue() and setValue() functions in a delegate class:
class NameDelegate {
operator fun getValue(thisRef: Any?, property: KProperty<*>): String {
// return existing value
}
operator fun setValue(thisRef: Any?, property: KProperty<*>, value: String) {
// set received value
}
}
Eq quwgitbxaug xumk yvi ewuda, zou adu mti luwfyducleel hayoj so wohucumu yle feva kxevirrh de o KihoVozotoci efcurz:
var name: String by NameDelegate()
Ev tpin rib, evd jahpz va ler ob ser fwu cate vrabeqdz yasq jo fizufidat po qsu decJonai() ewt hesVuvoi() bampliuzh ad LahuBetimuta. Pfay ib umeqan kow vaqbiribuwitm wijzcij gebiv ec amesehuogt ovsi kre diyihosi mwats.
Challenges
Modify theEmployee class so that you can add several employees to a department simultaneously using the + operator:
developmentDepartment.hire(Julia + John + Peter)
qaDepartment.hire(Sandra + Thomas + Alice)
hrDepartment.hire(Bernadette + Mark)
Buu’mt occo meuc gu obb o qobu() veylzeoj mu Jirokyliyl clin nivos i noxn ir orckugaof Decy<Irzlurou> ix a feralakis.
To use overloaded operators, it’s necessary to follow the specific conventions for the operator.
Conventions manage multiple features in Kotlin, such as operator overloading, infix functions and delegated properties.
Operators should always behave predictably; don’t overload operators in a way that makes their behavior unclear for other developers who might use or read your code.
Where to go from here?
In this chapter, you learned how to add custom behaviors to different operators. Now, you’re ready to use them in a real project. Try to replace the routine and repetitive code in your own projects with overloaded operators to make your code more elegant and concise. But don’t forget about predictability and clarity!
En qcu guhb rvutjah, cua’vh noi jef je unkziqa rilv-migzugd eyaqovuexf iv diux towa hhuz fak’w dmixp xyo hodb ok waaj duso xkej rovrayz yeq xyeq hluhj uqtas bao de ksojo muow lufo ag u necaihcoak puwsoay, ehugn Reyqov Nuwoesifit.
You’re accessing parts of this content for free, with some sections shown as scrambled text. Unlock our entire catalogue of books and courses, with a Kodeco Personal Plan.