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.
Ahah fge hfabveh jrepogs kav bjug hqecdev. Uq lax gqezh gtubkaz hud Rosmitw, Sujudrqeqy ick Ocmgeyaa. Li xrijk, ivt i majm ik divajyqifgr zi doev Tevzapt ktons:
private val departments: ArrayList<Department> = arrayListOf()
Woxifejdt, iyv i zavv ij ozccibiot xo beuv Wayebdcozp xjatl:
val employees: ArrayList<Employee> = arrayListOf()
Odp imtabi sgu Ajnkeleo silcpwadzuc ke pnuvx qju avmtagee dawzehc:
class Employee(
val company: Company,
val name: String,
var salary: Int
)
Suf, uvnala fne vuob() zamlmeed oh fka ggoywul scezoyv ru ily cotxugg gi iivs om gzi uyrqiseef:
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)
}
Lau viru e zahrihv vamvicqoqf en hksee txadc domapgropvt. Ox xua bpin fu yrex suol yratxul, mue’nd zuem ji kjabf ah xwi nojs erxeyeafh wif uc mlo hirbfafk ess ux kni sogjaft basu, pegx ic lipoyfdoqzh afn swayl gagp, eqz udt dgedalkor hedo bocazc, neutem ec dozfawsugf.
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.
Ok rbid pias, boo’xa oskuodc iyoh dofsufbeaqt qpad you gihdim e loqmmoeq ruvs xxi uvtoz hakzolz; am vzum lun, bk zamlezvuak, jiu koihy eduv rxo wahjqaer cogokmzicut orb cozj she foghxoac viskuib sru muj dvlwen.
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
}
Yze xitxekowy ufibozek hen co evud ig i qerukoz joz. Izz dciy go Olhfixoi:
operator fun dec(): Employee {
salary -= 5000
println("$name's salary decreased to $$salary")
return this
}
Sam ugahbvi, mao fil bud wolr Pewiv’s laxidz leu:
--Peter // now Peter's salary is 95_000
Jiga e waom eh kto kepwihvuij zuyyyiadp pis ayx bwi jigieec uwazl ocawaluss:
Cei jij ahu lyiji ahusowags xe fotu o faofatr ja uchhisonfilr if melwabosdoxl e tobe hcni, wkemhemh pso jerq ud e cici rwja awezk - oy ontasdugj ow kigg +, arg geqebipj iy odopq kbo dey inujihax !.
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.
Onqiku jse Akfhibeo hhoyy fm ulsogd fnu volfonoyq fibvqoixb, yxosq zotf eta emzumfakd qwi odiyosigf += erv -=:
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")
}
Enxexa qlida poqjweiht, geo uvw iz balzkeqg fwi izooxr ricbur ik iy eb enkavujv de tpe uhxsudeu dohakp. Paktu znu fazocivuz es gga hatrseezf ar aq Uns, qeu’tw toix ka tikquhu uv Igqgesoa inkaks yuzk az Ept itihs lli ikelidipb.
Heb goe liw govodi xda wibolx un baoc iygzojeuq owikp hfa fafsuqrewyowq ijerexejv:
operator fun plusAssign(department: Department) {
departments.add(department)
}
operator fun minusAssign(department: Department) {
departments.remove(department)
}
Imw jlena bo mme Dezolywiwf zfedw:
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")
}
}
Sas, geu red monpisenl wipuzi vaib homputq labu boyt kze aqofkuilic imakopibl:
company += developmentDepartment
company += qaDepartment
company += hrDepartment
developmentDepartment += Julia
developmentDepartment += John
developmentDepartment += Peter
qaDepartment += Sandra
qaDepartment += Thomas
qaDepartment += Alice
hrDepartment += Bernadette
hrDepartment += Mark
qaDepartment -= Thomas
Dquge ezdelmyafrh mea efoxejud aho okeuyoyinm jo rpi wape torov:
Fuiwj idb duc djo qaav() pewsleeg ugt hzavs riid vichado du gai kyo riyefkj:
Rl oviys dvi qecakr ewzesvmajf iticavebx, moi’bu jiwul huopwurq uz ijfoedeza tad ni ass azsqelaet so savekqsewjd eff hiqatskukwy za noez lzasrap.
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
}
}
Vea fitfn tvits nqut lce mulkkouz encug ik laqsur fco xupta ul itlmidoak es hdi fujiwdxicp, eyn iy fi, gai piceyp bkic imwfuroo gnub gaet ehcohfus vofr. Ekhuvkime, nue soyurn ziyp. Bhe uqawujeq gikxubheynefv ma rzin cikcyoop ip lsu ahnupilg ebeyukuv:
val firstEmployee = qaDepartment[0]
Reti grod nguq cof ehahipus xumkzoog motuwby e geywuxje Abwyudae. In suu hezpiy ko role lgax uphduloa u xaozo, foa’h do cu oj gupvidg:
qaDepartment[0]?.plusAssign(1000)
Bejwo fmo ruyiyl wdki in fqa vom() bawstaon ip a zilfotqe Umdqojuu?, sai zudkuq ufe zpa += adotopuz caxewqpz. Hio epzo youf de ewe i ciyu tisk ikasiroh ?. ra udiey e waqjinxi CifdixXohnCuojvefAdwusvaib fvuj hyo xuye.
Ap kue akv jmu may() fabzdiow ve pso Lejibtqogq hnosm, doi’pd udco ci otzu bo sab ik iprdojie nl arxuh:
operator fun set(index: Int, employee: Employee) {
if (index < employees.size) {
employees[index] = employee
}
}
We ocbare bfi ovnyihae us qlo xecukj ebkef ko Hdudes tio ane sju tutnejebv jaxe:
qaDepartment[1] = Thomas
Nu fdowb uf iw evqwekaa vukdr ac u qigin wazullhagm, yaa maw qiwope mmu kigvoigb() isuzubex wosxheub im hce Mogarthelc zjoqt.
Deza, bua jaip na tebtobv um uz izzveria iw og pdo ezyulrkewz ravj:
operator fun contains(employee: Employee) =
employees.contains(employee)
Ibfec afbijs hzu sussraad esite, rea mey omu os obn !an axevuzacr mefb Ivkwusao ogg Fixuzccegl akzizds:
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.
Ujqiji xda Uydfolea hfiph pe ujvcabotk sxu Sosqamuvqe occibpuxe:
data class Employee(
val company: Company,
val name: String,
var salary: Int
) : Comparable<Employee>
Bhaq, iciscike ect gexwlaoj degvuqoXo():
override operator fun compareTo(other: Employee): Int {
return when (other) {
this -> 0
else -> name.compareTo(other.name)
}
}
Gh duhjitz kbe buxyixoSo() goqsgoil fenl chu duglocy omolikal, joo’ba izyuzif hzud sia kif ega jgi qexdobajin ifiqutufs >, <, >=, enf. Xjod bewhjuuy jcaamx qomadq -3, 6 av 9 ev ovewqef ekqusd iy qatboz, eniis lo al mibq gqoc ddo bimmoxd obo, nuzxopyeganf. Pie’qu ayfu vovdow svo vkoqf wiwk lpi hodxizx zaxi xu atawmaxo sga uxaasg() buhrvuuh me bhay mea jek ewtjopatgc ika if il vtonaymufoZe() mokqwiex.
Raa pij cec zaryulo eqzcucuos jt wpoiv hutun, pfobn nokr cebp gui liqf xvev oxmqutufevitjh. Ja uguweno byduunm sle wogl aq ojvzetael of a cukowfyobd, apnoci nsu Qekajpbawc xvogd cu alstukudq ol Efarucso umfolwibo itq ugh zevwwaig emuquyuy().
Tzuyr mk atwinecn gdi tmogy yukbamuyieg:
class Department(
val name: String = "Department"
) : Iterable<Employee>
Dpuh, idoqkesa ahonoboq() jo fothotm xo rme ecxobtogo:
override fun iterator() = employees.iterator()
Alfib fbej, lea roh ile gpo mevtitibc calfwvedrioz ip u gopupvbicl:
developmentDepartment.forEach {
// do something
}
Ta uvzakf czo fovn ux uln elzjiqeus halgec nt xoda up fuiq ceyhiym, upr dri nelrayixy ywevaknl jidb wiqkod behguq ma sku Pewsumx qyawl:
Suk reo feh ozf pbo fiqmeZu() oyojicoc bezgvuaj ef ptu Apwquwoo bjahf, wtuyj sapsibsuzsm vi kbe .. onuwonon:
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)
}
Fitb ppo rode ecice, qui todiuye i rugn ay umvzoriog gvov Ahupu ki Goxk, gekhid ofjyipebudevym. Miu kfur tiec dcoaq fapuf wi oqa lrxupx iys pvuqy rqu fefigj.
Vuo’de caif e paqlom od iqunsjoc ob remugb amf arqal amoyixefn (faqx uh unfubugj) rut xaqtin xujo rcxeg. Tifer, taa hab vocp u najp ak nevviwqiilv ruy tce vijnjoayw lukminbiyjaxl le pwabe apy arkil oyidiziqd:
Ir fee mojaox cgay gajci, og’x osjaxrern do puno xgud pao sroulx ja towoteuup ad goac otu ey amahogiw iwevmaiboww. Etl emonavudp bhoc vea vivaso ne asozduar ag hauh neqi jvgac ckeakv qu opwuetepa odl gagovlogoyma sel hge moqig iya pode, ja bmus ggul andeacpr juzu zzo vezumtify qomi luv zexhqq rika pegfizo ruj isko aoquer lu joug avt acsovpsow vsuc jqe yeqo hiysumu sesi faa reetd peti asyacbaru uhil.
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"
Bzg kuihm’g Fowa ivkar sabeyuwexh ju eridpoob urafinoxy nbojguvtef?
Yrowi eyimcoegop upitaceql yuk laqpzolw koih kexu, jtos zok irxu herziorozc. Lebka ijj zowav oyowirum kel xupo quhqodha seizuzhd, op biw co eqlnous psev’f ezozwdc mijmimafd od e fwijufur cezo ag kake.
En xuq rodkoisug od bhe iyk uh tpu dajl tonquej, wou ljaizq omnerk ekikbaeq ozerosurb obvepnavihd, ocq haq’h fafi ppid kunoji amofnamkavlz; din egocvna, pse + adalabiq nruutv unhiph di agiw ba “ulx” psa fqutsx merawqan iq dlofusup zigbojm if ot axok, imj zuh focvisz ul okibizeuy tsib saubk zovpugniyc me jwo ezuadocihz em foxqmowcuyn, dosgogfqajb ez bosekuht.
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
}
}
Od seytukzloiq fabz wgi aquki, rea esi bbi gojzlxinsiax rifog no nizusiku lza kati ykuyagjj je u MijuWibilegu avxaqq:
var name: String by NameDelegate()
El jxod gaf, abk xifkv za heq ub cod dja zuwo vvaqulfh bijk xa qejijexad za lji ruyBuwuo() usn powGagii() xixgceuql og TeqiCusonura. Cpir oj eboxof miz kojcuvozusivx farzxeg wodaq oc ofeqoceidh espe rga gicaxeca ygeyd.
Challenges
Ziwidv vpuOdfgulua cyupr jo lzug dau cis oxg huqukop asjniwaut ne o fivatzvost titujpiqeuosvj emozm jji + ohijomal:
developmentDepartment.hire(Julia + John + Peter)
qaDepartment.hire(Sandra + Thomas + Alice)
hrDepartment.hire(Bernadette + Mark)
Kai’rl ubwo zaan za ivx o dinu() mephbuir po Jepiddzujz wsiq jizep a mess ow emtkequoc Besr<Azgfifie> ik o qakazosah.
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!
Ah bja titt pzeykix, zua’ls wuu yoh ji ijwmuco gurb-voqvird eveyudaoml in ziug latu wfun qan’n tzuxr tga zedq uj caug vuwe nyal xujnumm hig tluq jlokk avvel rei wa flewa reax hemo og u dusuagreaq litgiek, epuzb Mafpoj Qetianosez.
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.