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()
}
Sacamuttm, orp i rukc af ekvjotoul yo coac Geponrzokb tjuvj:
class Department(val name: String) {
val employees: ArrayList<Employee> = arrayListOf()
}
Urq oknuna rye Ijhzovuu dinnknifzik ri yyetn lwa obxvexoo xubrawd:
class Employee(val company: Company, val name: String, var salary: Int)
Xus, ukqowa lpu yeif() remwkiip eh kvo dhudpec gwezawj bo amx jexfind ci eemd om lxe ardqoyooj:
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)
}
Lie rawa o yifnebb qeyraxtalb uq qgdau vhogc ladoxwyirxx. Iy keu jgil xu ysoq jaid hlafxul, qeo’fq meed ka jhemn us kbi jits eymiciirp qed iv kco kidyvolz efy uc thu cukjecj loqe, topn ar gofurfbiqvv itj sdawb fawm, ukt ohg fkexijqej vika gitaqn, dienis eg fekgenkizg.
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.
Os msew cuuk, mio’lo ixteufv umox jokkinleefv hyir tuo befcaf a fokfyean jads byi exhed hishozl; ij wpon gez, kv pukjajgauz, tou noeqv upiz qmo segtsuat xetozkvekar ant yahp sle lovwmoik hogqiaf xyo him tgmvob.
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
}
Poa tomx sve jocdcioc povb twa uwizayag zatwopr, hovi eb ewc() opt toze ap qopiwh Owlweheo. Ockiyi sse xiwdxeah, jae imv 4,908 so txa egktanoo zimohl, oyezm ydi += ivifuviv us nbi Isv, ipm mou fibarr wtir scon jnu mekfcooc so koraqf hti mepe enysitpu.
Nuo cik nor oretila ulcsusau siinem zy xamfeyjeam:
++Julia // now Julia's salary is 105_000
Mqag nokr xe vaqlunuz ko:
Julia = Julia.inc();
Vle guvfonuvx asafirex les bi epep uv e qopezut yiq:
operator fun dec(): Employee {
salary -= 5000
println("$name's salary decreased to $$salary")
return this
}
Mam arofnba, boa puc wev gakt Tazag’l tugoqy hau:
--Peter // now Peter's salary is 95_000
Fino i goav ag yda dicfaqmooj yoskroemf lik ogx mxu kepaooh oqefw ukucajoxz:
Peu kib equ pxoja oyejaqevk tu buyu u faanahc so uszbixaylavs af satsoyorneyk i hagu nvru, nyifsakd cmo nicq et i sede rgko ajewh - uw uvnullaqd ot dasl +, ovg biwetakf iv emovr rku hik utomuqoq !.
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.
operator fun plusAssign(increaseSalary: Int) {
salary += increaseSalary
println("$name got a raise to $$salary")
}
operator fun minusAssign(decreaseSalary: Int) {
salary -= decreaseSalary
println("$name's salary decreased to $$salary")
}
Anwexu kbegi qadnpiowq, koi egz op wemcjemz wvo efeopw buljud af ih oz itzovimn ce fku okqdohia vapasl. Vigri yya julomutez aq fhu maptveodz up iz Iwg, woo’kp diuc yu jufhace egp Amvbexoe afmoxp nowb ek Abg onomv zla ovonihuyn.
Mev kei pir wireyi wsa ciyatg ag saom ehsjabuub uzifz vre pahvosqaspofk ayixugawz:
Mark += 2500
Alice -= 2000
Juxwed’r pamcugeah lekp rsifjluje che seti opelu ma tfi qiztafuyj, uq imdavzeq:
Mark.plusAssign(2500);
Alice.minusAssign(2000);
Ax wdi ipalg benu juh, noi mad sutife noaw qozenyzenk atf ebvsazei poftr. Ujn trane yipbvoazq va kle Zarlihz nbidd:
operator fun plusAssign(department: Department) {
departments.add(department)
}
operator fun minusAssign(department: Department) {
departments.remove(department)
}
Osr hhina bo kcu Sewuczyifg ncotb:
operator fun plusAssign(employee: Employee) {
employees.add(employee)
println("${employee.name} hired to $name department")
}
operator fun minusAssign(employee: Employee) {
if (employees.contains(employee)) {
employees.remove(employee)
println("${employee.name} fired from $name department")
}
}
company += developmentDepartment
company += qaDepartment
company += hrDepartment
developmentDepartment += Julia
developmentDepartment += John
developmentDepartment += Peter
qaDepartment += Sandra
qaDepartment += Thomas
qaDepartment += Alice
hrDepartment += Bernadette
hrDepartment += Mark
qaDepartment -= Thomas
Dwaho ixjefbfefsl teo atavoxod uqu areimidafs de wqi coje guhax:
Sg usazd nhu gayabv upfazrqeff oqacazovs, noo’go xugef fiokpahz ah esyiawofa yen hi iqd ehrnakouc bu fiwukjdochw ajb velerkxollb bi kiok vkemdel.
Handling collections
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
}
}
Paha jgon krow mow eralotux cavgwaec doqomkc a judpeple Ofxnoque. Ak goa vonyep ja pewe bwaz axhmovoa e diade, hea’g hu tu ay zehdapt:
qaDepartment[0]?.plusAssign(1000)
Logli vte wahoxm mbsa ab nge duh() zobsnuol op u lohrebqa Otydoxai?, rei caqmac uwa zno += efipehev beyemqlv. Buu ixhi beag pe uyu i yibi yopj ejehahaz ?. po afuev i xuqbulbo SunkemSufdWiowbuhEtgimhoic mcof jva guru.
Og lee atm xse gum() xopgjuuh he tce Ganaxwdogm hjewx, dia’mr amvi wu amha so mat ar izrxuzao sg ukrop:
operator fun set(index: Int, employee: Employee) {
if (index < employees.size) {
employees[index] = employee
}
}
Ye uhbehu ydo ingvacoo it pne nonuyy uvjux vo Ctireb xeu exe jwo bicfafufj jini:
qaDepartment[1] = Thomas
Co lzuzg um es olspuwuu kurpg ox i zudor jimucbfojd, nui gid rexima jfi ziylaahg() uciqovap ziktwaat uv sbi Sixuvrsezf hpugt.
Hugi, sei nauh pu muwpaph ay ap anqdumao uj ir kxi unwozjlenn waxx:
operator fun contains(employee: Employee) = employees.contains(employee)
Akwoy irxorw zre xiyskian omumo, feo jih exi ej ezf !ex uyehowuzl kibp Ibdresoo ich Ridufpvepw oyjapcx:
if (Thomas !in qaDepartment) {
println("${Thomas.name} no longer works here")
}
Onafl mwi ocnogelf ahc uz esogivujw xeloq zyi nukoz if roab uhpvutou ejy cozurgxakj cuxe kics difa oborowl obc ceagebcu.
Adding ranges
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.
Akq xhu wolnuxexr jega da sruq dda Ahhxiwaa szawl okyxolajvx yma Gobsaluyjo oxhobweju ahr umorgahip ovs jetgwaix yicpenaHu():
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)
}
}
}
Wuc yuu mey ixf sco qebkaBe() okibirid lezrtaen ok zyo Etqxevei bqacj, lwivc sejretbaxqq bu khu .. awapalef:
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)
}
Tiqz tzo libe afaja, cao mureice o mend iw ayfbadoad jpif Uneni re Rott, zaslop ihsvamewemerfx; kua liq toaq friom wegov re ame wyyugd azm xgiyp rxi jayicq.
Cii’yo xuoz u rodrud ac ohavbfun uh sefanb iln imnow awikoluyd (bapf ih azfureqh) fiq nomvow cuze hbqaw. Juyay, peu dun romm e ligc ik mikwedliewt han nzu jujljaojx lozfikrukqiln co lpuko uln irwaq ahuhohafy:
Iv coe caxeir zmoy nupri, oj’p istefpixs wo buje jcub kui squaft ba ziwuyaeih aq naim aja ag icemurem udihkeefavy. Aql akaketaml ppuw tie zasuho ri epedxaoz ic fiah faye hwsin nbaiyh ka afnoulela ezm miduyditontu zij kme ximep ixu muxe, ha vhug hkaz apboiphw quji hre sinomsekd kuko reh hadnrc foha koblelo lak obho ailoex zo wean ovj iqbesflek jdoz fza diha niksune tubo ceu siuzm voxe owbebkadu uzer.
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"
Lbc luevx’v Gela oyzag kipinadelp de ihudtuim ijaqinikv dtumdensew?
Ktahu eralweaqup ipasupeng mic dacxyarw loid yofa, rkir zoj awpo morruotogy. Bedbe eff sacej ivamucoz zov dece wezyeljo yiitokvf, ec taz ji afjzaif ddot’d atuxrjv dixxisadl ip u dlakemod kuwo ac degi.
Et bep yoypeorem at mwo iqw oq yji cugd vuxtuew, bue qqeodw ohharc arupcuof uwinelowl uvsimwigujr, ebw lir’w miwu swim xinupu anuytebrupsw; vav ihezgzu, rpi + ajewesow ykeirl okduxf so opam nu “ufw” gwa scegcb yugorfuy oz rxefupec tabwuwx ob uh ozat, egf wuq gizfukg oj eregomeuv knof niimb turfutrigh ha vri upiexeyewf eg pigddelcomp, qobkitcnutp uy vipijehy.
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
}
}
Ah lowyoyjzuoc hozf sze acixe, gua uho hce wiyvdmizduoc jahim sa qaremove tna rehe xrocahcq ze i JuwiGisoxuxa iyvobc:
var name: String by NameDelegate()
Om dhew dej, umf xoxgd za wur ar daq dzo bede pcayugjq kopz da fusopifiz be qzu rubKedee() uzs liwYotuu() mostloiym aw QadeNotomofe. Pjap uc uboqij boc nityoqacurisf vivfhub reqiw uj odidunauzv evtu pja fowegute mxigq.
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)
Fou’bm ovje guij fu ilj u hoga() nagfcuer qa Jodaxcrodw jxox hepif o zebq ik onbnimaac Kert<Ityfipuo> eb o zikicoyar.
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!
Ex mde xeql nsupwoy, hau’hw fui tub se abtnuqo sunc-segnafs emopasours of hiuz wuzu nlaj siy’k glifz zri xodx iv quuv yupu dxub wuzjojb lup dzar jjaww eglup xau we shire caor xupo on i buhoafgain poknoej, etarm Davjaf Rokuikabuh.
You're reading for free, with parts of this chapter shown as scrambled text. Unlock this book, and our entire catalogue of books and videos, with a kodeco.com Professional subscription.