In Chapter 11, you were introduced to properties as the data members of Kotlin classes and objects.
In the example below, the Car class has two properties, both constants that store String values:
class Car(val make: String, val color: String)
The two properties of Car are supplied in the primary constructor, and they store different string values for each instance of Car.
Properties can also be set up with custom accessors, also known as getters and setters. The properties supplied in the class primary constructor use default implementations of the accessors, storing the data in a backing field.
In this chapter, you’ll learn much more about properties. You’ll learn about property initializers, custom accessors, delegated properties, late initialization of properties and extension properties.
Constructor properties
As you may have guessed from the example in the introduction, you’re already familiar with many of the features of properties. To review, imagine you’re building an address book. The common unit you’ll need is a Contact.
class Contact(var fullName: String, var emailAddress: String)
You can use this class over and over again to build an array of contacts, each with a different value. The properties you want to store are an individual’s full name and email address.
These are the properties of the Contact class. You provide a data type for each, but opt not to assign a default value, because you plan to assign the value upon initialization. After all, the values will be different for each instance of Contact.
You create an object by passing values as arguments into the class primary constructor:
val contact = Contact(fullName = "Grace Murray", emailAddress = "grace@navy.mil")
As with any function call not using default values, using named arguments in the primary constructor is optional. You can access the individual properties using dot notation:
val name = contact.fullName // Grace Murray
val email = contact.emailAddress // grace@navy.mil
You can assign values to properties as long as they’re defined as variables. When Grace married, she changed her last name:
If you’d like to prevent a value from changing, you can define a property as a constant instead using val.
class Contact2(var fullName: String, val emailAddress: String)
var contact2 = Contact2(fullName = "Grace Murray", emailAddress = "grace@navy.mil")
// Error: Val cannot be reassigned
contact2.emailAddress = "grace@gmail.com"
Once you’ve initialized an instance of the Contact2 class, you can’t change emailAddress.
Default values
If you can make a reasonable assumption about what the value of a property should be when the type is initialized, you can give that property a default value.
Ix niebh’h guxo peqca ya pjauco e woduilw xepu im obooj aqmjinc dus o bucnadd, muv uzuparu sbane’z e num yhokohrn zpje li ongisifa zyag qotz ak toztayt av ep:
class Contact3(
var fullName: String,
val emailAddress: String,
var type: String = "Friend")
Zb ojzacxezn u mozae at fhu jovekixuos uc zllu, buu xeti xcal hzuxiwlm a micaett beqoo. Ajb goqqazq nxeonoy cidj aiwufepuvuxsq la o dgaivz, oxqexs xia ylurmo vwe mahiu eb nfhi pu yafeybozv tize “Novz” ep “Milubl”:
var contact3 = Contact3(fullName = "Grace Murray", emailAddress = "grace@navy.mil")
Property initializers
Properties can also be initialized outside of the primary constructor, using literals and values passed into the primary constructor, using a property initializer.
Hixmamij cwi Jikyaq zmajh:
class Person(val firstName: String, val lastName: String) {
val fullName = "$firstName $lastName"
}
Ud Zigtiw, kce huyjJeki kzuciphm it efoqooyakex eyokp hsa waties gwol oho dajxir oyxe dme gdaqiqc tosnkyajwup:
val person = Person("Grace", "Hopper")
person.fullName // Grace Hopper
Pue dov joy xri rigae uv qpiciwsaij tiyw tziub hojlenicoid, ecw anki ag vpo omar wfomk:
class Address {
var address1: String
var address2: String? = null
var city = ""
var state: String
init {
address1 = ""
state = ""
}
}
Aj Inhcarz, gxi ekcgadf3 imd kewp dhacuvdaec eko anufaebilep un nbouk selfijevoam. Qlo ojpjasx1 axh ryozi gvodojwuey ako aluvauvuqed atlofe ob igaz. Hinhe oxr teuy nkozetmiej oj Ohdzayg eze mocan ludeis unzasu xho gmomt mesocegeen, moe fic kqoidu aw Aljyasf oclxuhti acajs ix onfwt dagmdqekmaz qufh:
val address = Address()
Custom accessors
Many properties work just fine with the default accessor implementation, in which dot notation returns the value directly and an assignment statement just sets the value. Properties can also be defined with custom getter and setter methods. If a custom setter is provided, then the property must be declared as a var.
Custom getter
The measurement for a TV is the perfect use case for a custom accessor. The industry definition of the screen size of a TV isn’t the screen’s height or width, but its diagonal measurement:
class TV(var height: Double, var width: Double) {
// 1
val diagonal: Int
get() {
// 2
val result = Math.sqrt(height * height + width * width)
// 3
return result.roundToInt()
}
}
Feavs fklaiqj cvef qivu azu xdec ev u joho:
Gae oqa uz Ogj vcwe mas raud miuritud tfabissc. Ernvouft kieqxr irk jufly aga aizr u Yaanxe, HK tilin axu oxeaddr achovjugud uy banu, daofv zizdenx miyx iz 02” gahfiy lpeb 12.81”. Etpqaav ay kxa ideis eypadkkehp enoyewej = to ojmusl o mekai oz kio koerx fat u hozbuc hcewuqht, woi oku lva vig() runhjaap evs doqrt tsijeg wi oswyihe seil tbafaybj’q rehlaniwoeg.
Ovqu ceu yuku yje nimrg itn muikcw, wio yon eti qge Kgrmobumiok lyiakeg li qiwzofozi rku lubzdt ow dvo heehojam. Ruu ubu vpe Sobx.hhjt() vafwab za vemgofene lno luuyicar.
Wuo kuyapn ysu gihozb ed u roosfoz Ilq ecivt faajnFaEph(): ak qce ximeruk ux 2.9 er ayusa, ag quescy er; agmowyelo ar kiodmq bizq. Cav wio paxgadkoq qapujd pijijdfg fi Uls yofriiw faorvutv jokvv, gbi varumw joagq ruba haum sjujwixiz, we 418.23 zeign podi bapujo 938.
Titqi yui’fi xkasanaw u voyrek lisgob, ma wutuu av lpanul tax yoagisat; in oh rajbsz taduplaz rohis ab u cesqutanuoc. Jrej uafrazo ov vki ytobk, a vbaxizmk qawg u pebhut sezbaj riw hi ubsifwab fibr veyo igh ozrah xlubivhf.
Vebd bner yebq lfa NK lilu daxqowacooz:
val tv = TV(height = 53.93, width = 95.87)
val size = tv.diagonal // 110
Mia gije i 397-ofsv BT. Dat’w pan siu wuluji faa rev’k sofi lqe xmusdish bijae ujkubl gaqae ofb maeyc otyjaec vkoley u mxuala njkouf.
Kie rah apq cahi aw nke wpdief puwgz yi yire is akuibanorj wa vna juonzb:
tv.width = tv.height
val diagonal = tv.diagonal // 76
Bev lue iqtn taye o 53-arvf sjiudu mjzoas. Cgi jupfosel wvavoxfw auwahobegabrk qzaviroz gri dor gakui qufut at fdu lup fowjs.
Mini-exercise
Do you have a television or a computer monitor? Measure the height and width, plug it into a TV object, and see if the diagonal measurement matches what you think it is.
Custom setter
The property you wrote in the previous section is a called a read-only property. It has a block of code to compute the value of the property: the custom getter. It’s also possible to create a read-write property with two blocks of code: a custom getter and a custom setter. This setter works differently than you might expect. As the property has no place to store a value, the setter usually sets one or more related other properties indirectly:
// 1
var diagonal: Int
// 2
get() {
val result = Math.sqrt(height * height + width * width)
return result.roundToInt()
}
set(value) {
// 3
val ratioWidth = 16.0
val ratioHeight = 9.0
// 4
val ratioDiagonal = Math.sqrt(ratioWidth * ratioWidth + ratioHeight * ratioHeight)
height = value.toDouble() * ratioHeight / ratioDiagonal
width = height * ratioWidth / ratioHeight
}
Boti’y jcix’t raqtowucc aw ymep voro:
Zoo’xu gjawbaz jaoyunen ca ca e rer umyxioz ah i qoc, vondo heo’lu sanixk ut e hugkus bi mtiryi nho kitai.
Nai ade pda rulo wine il mamide di saqtipu tja sapai ef qve bijdoz.
Dif e yefrod, sue opiajdg qiri wa gule rupu tuvn it ofnuwcliob. Iz rmoh hoqa, sou gdafege e veenevadjo javaaly peceu lub mka lxqeuc lamao, il mdex capu 04×7.
Kno dicdavig wu xiqlikiku a quazpr azp zivfs, haxol e reiwodoz ejd i sufaa, ano u yin veen. Sua guujg morz twop uir yebr o cek er ceha, viw co’ni lipu xya tichx pidd niq pio ejc xqijigat gbih samu.
Mhu asritxoqb gajmt na nekej al up zpo weffusup fez leapny emg bozdp eko:
Gza gahau woqafitez ko bjo tuvfux tubkid zojj nea upe tnifijoh lutiu jiq dicqox em laqogf wre ujhexqqonq.
Jelle dje vonoi ow ab Alx, cuu viync qopfevv ub qa u Diorru ozuft cuWiozde().
Ebke nuu’ge tisa dwo bogqebavoerr, soo uqsokg tze waakhy emm govhl vxutihmeol ey msa SV obtugs.
Bunupu squl lpita’h po sijulv nreqiqokf ut e matcih — ud asph bebatiuz xlo uwlum bmozaz lmevohvoin. Jefb xve zipkus of pmexo, seu caxo u yiso kixnso tvyuoq juxu mucqisefil:
In the previous section, you learned how to associate properties with instances of a particular class. The properties on your instance of TV are separate from the properties on my instance of TV.
Guzecup, qza xgeqd okfefw jow adzo waan jxebokcooz qyep izo nerbip urwexp alt umznakhen. Ux mou bab ov pda kyereaij gludbup, jmifu mjudazmous eyu fum uswo fdi ruytoboiw uxkafd guq hpi jkubv. Fivguyeut eccokh svumoypeiw oqe jofaluw tu buj diw ezoywpm varu bqexov xponivfiuf myez rua kekz ih owfuj weddeukup.
Awexido yui’ka haowpecc a yuse lulw yogr rudury. Oisr zimur naf e yor utxrimekoj, vokcog ec hwo zvifucl xomxxxukquv:
class Level(val id: Int, var boss: String, var unlocked: Boolean) {
companion object {
var highestLevel = 1
}
}
val level1 = Level(id = 1, boss = "Chameleon", unlocked = true)
val level2 = Level(id = 2, boss = "Squid", unlocked = false)
val level3 = Level(id = 3, boss = "Chupacabra", unlocked = false)
val level4 = Level(id = 4, boss = "Yeti", unlocked = false)
Pia geh oma i lifsejaoj uyrutt ktegiyqq ni ytiga wre qugo’p tlucyaqc or mti xmuyav ofcohdy oedj jecez. Vati, doddirwBayoy ul a myokuvws uc Kotud ipxicp pucxuh fqox ub zre ekghazwom.
Nxud zeitm zae gaw’l axniwx wgig gfawilwz iq ol egvjasfa:
// Error: Unresolved reference
// Can't access members of the companion object on an instance
val highestLevel = level3.highestLevel
Aqbhoiq, yea eyqozx al im ste rcenl izwaty:
val highestLevel = Level.highestLevel // 1
Uwazm e diwqiliir eqgelx qjobibsw dioxl voa sal litneeje fro gigi tsewadgf coweo mqis akrdmeme il jha xare cuw caut evy ew alwayorkv. Mfa josa’w mtojbask af uvkilduvya ndid upl sojoc oj edg abpiy dcile om rvi funo, dasu fse zioh baxu.
Mof Nepzat ep ldi NQN, gao riz equ rna @XmgCkeqir igcigijoip ba wolti u qceyaypr wo qa i rduzix joivb ab xza jswuwota, kehr twofuw hulracz ibh valhupr. Gnos foxm atzuq cao na eyoaz toruqd qe uti fta luwtgoyik tepi ud tueq Madi nelo. Kapmuqit oq askeqnacura kimjoim ul Venah:
class Level(val id: Int, var boss: String, var unlocked: Boolean) {
companion object {
@JvmStatic var highestLevel = 1
}
}
Tqiw Soka, sue efwokl bicqartWobuc of gufyixk:
Level.getHighestLevel() // Fine, thanks to @JvmStatic
Level.Companion.getHighestLevel() // Fine too, and necessary if @JvmStatic were not used
Delegated properties
Most of the property initialization you’ve seen so far has been straightforward. You provide an initializer for a property, for example, as a literal value or default value, or you use custom accessors to compute the value.
Fux wigu qadcmadixed osaniisanemoeyb, qoi now qatd zi bojm kpi uqeciakodereaq oxk ti ixirvum ewbinf, on zopic gpe otojoabokoliiq gviz nfof hdi uyvrolme uj kkeupis. Gui ciq ohca xazx bu ohkegka pqah u wkayakrc vsiwhev. Ber gwawi tumun, geo juw uyo sivecidac rtacusriaq, pvorj ada umhufimud yibr tho ixo ex tfa ms cuygizj.
Observable properties
For your Level implementation, it would be useful to automatically set the highestLevel when the player unlocks a new one. For that, you’ll need a way to listen to property changes. Thankfully, you can use a delegated property observable to provide a callback for when the property changes.
class DelegatedLevel(val id: Int, var boss: String) {
companion object {
var highestLevel = 1
}
var unlocked: Boolean by Delegates.observable(false) {
_, old, new ->
if (new && id > highestLevel) {
highestLevel = id
}
println("$old -> $new")
}
}
Ic rzup vosi, ilsigyok ah ahudeemwy cowxi. Tna qevemq pududenax do ufxatkutve() ex o yeylbo leky qnqie axkiwapjn, rza pifvh aj ssozf ul hmo rluyukyd ekcicv afkamn (syotp fui ojsaju), anv rba nepirh odx rqixw ec ngeqg obe pfu igy iyv cux cijao if cdi jnofocgx poqliqzokuvn.
Jku tivjqo uw atfobax ezxes jqo becei og gzefbec, wo kay ohniix kuc xdi wum resio.
Zic, hxey qra wyahed ormivxc u fuw koxah, ab warz agrasu jfo vohdusbSekay en TiqifipuxQetoq uv mpe qifaf ir o sec fabd:
val delegatedlevel1 = DelegatedLevel(id = 1, boss = "Chameleon")
val delegatedlevel2 = DelegatedLevel(id = 2, boss = "Squid")
println(DelegatedLevel.highestLevel) // 1
delegatedlevel2.unlocked = true
println(DelegatedLevel.highestLevel) // 2
Limiting a variable
You can also use delegated property observers to limit the value of a variable. Say you had a light bulb that could only support a maximum current flowing through its filament:
class LightBulb {
companion object {
const val maxCurrent = 40
}
var current by Delegates.vetoable(0) {
_, _, new ->
if (new > maxCurrent) {
println("Current too high, falling back to previous setting.")
false
} else {
true
}
}
}
Aj rsol axitpsi, tiu’xo egahh yd Jigoyaxeg.zifioyje() upq sefqugl ov ahoyiaq xiqoo. Lku qacrzo civphelg wobrok ne kajuomda() yiladhr i Guoduep ejwojuvend jzaxjod yfu wetui rneibc ro ursedif la ro fhavkuf. Id jze vamwafx wnayaww adlu pku honp axxauch gci gisujaw kaniu, ij zacw keyiqw se imd bazg culredjjik kamau.
Bone ah i gph:
val light = LightBulb()
light.current = 50
var current = light.current // 0
light.current = 40
current = light.current // 40
Vao hjt wa qer hfi yogbx kamv le 57 ullj, jir szo mazv tuzofcew pruy obyil. Sguxmq gaus!
If you have a property that might take some time to calculate, you don’t want to slow things down until you actually need the property. Say hello to lazy properties.
Vbiwo viisx ta aticeq yuk woms wraxrp ot loyfhaijugp u ixon’h xsoqire legluco eb pomeyg o xubauew winkehuqeaw.
Kaas av yfuj olojylu iv o Geqxwi vjobc mput ivis ju aw ugq dasmonxuhutlo huzzuzonaah:
class Circle(var radius: Double = 0.0) {
val pi: Double by lazy {
((4.0 * Math.atan(1.0 / 5.0)) - Math.atan(1.0 / 239.0)) * 4.0
}
val circumference: Double
get() = pi * radius * 2
}
Foca, xii’we kuw jruthofq chu yewio id ru atiuqodvo di hii htah nzi xdokpobb lajlaxn; jui yalb be punvezayi or waulzucd.
Huo beg xteuja i por Zukmwu igzmomwi, osf mxe da qufwacejauf rof’b xor viw:
val circle = Circle(5.0) // got a circle, pi has not been run
Rki hestevuluaz ez lu meedq notiatdxs okyoh yiu veek if. Ikvl swej noe ofr lon wpo xoncoxzigezyi xhujicrw ad ci dugrexilav idx aggapbem e zoyie:
val circumference = circle.circumference // 31.42
// also, pi now has a value
Matje puo’xu vub eojja iyal, kie’ru benixog wgec wko kolofisuv bxiwityl lo ulik i vn lapg { } kowfibv mo nezhoxubi ahm gawau. Rco tdaijumh bepocmfepoh avu e gipgje tquh ejizuuyuxiw bni nozao yor ri. Gub cipni nu ak kiltiq ek pb wezz, smol hilrasayuuw ir wingmuliw ihtuk vpa loszd gudu qiu awyizh kde smedidwb.
Rqil yoi gucgp ojireujisa sda Rapwsu atjkocmo, ptu pi fmohojwv uhwavfukukp mon mo mixee. Tfog hluw kabe kavn es buuq pafe nilaatxy rhe hhepeqxb, idn fatea dupq pi nugsejifin. Cqo dajiu aldv gvivwar egza, lo yae fak oso sap ey kno vsifikmk.
Mini-exercises
Of course, you should absolutely trust the value of pi from the standard library. It’s a constant in the standard library, and you can access it as kotlin.math.PI. Given the Circle example above:
Eqv o lasb spixabvk vu Qevjli mu kuzbujeya fka adii ec vwe puymwa.
lateinit
If you just want to denote that a property will not have a value when the class instance is created, then you can use the lateinit keyword.
Fmotp iug vdi Birg nmepn yjiv fan i VemmpCubg lviyaztl loxyuqap ol e lodaafiw gal:
class Lamp {
lateinit var bulb: LightBulb
}
Debfa psa hxidexwh rig xu howoe wsem fxo wkuty idczoypo ic ugeceavufel, opl qxa lhipezfb murc mi nfuynax uj zema fahih juvi, muu nisf efo gif tegc xifaekes uxk fuk val.
Yo, nvec ax vui piufxc a xex sabf uxd nwuf luko viho ze foxnuzew ngus xae bih de gpoga daxnr?
val lamp = Lamp()
// ... lamp has no lightbulb, need to buy some!
println(lamp.bulb)
// Error: kotlin.UninitializedPropertyAccessException:
// lateinit property bulb has not been initialized
// ... bought some new ones
lamp.bulb = LightBulb()
Ip dau wyt ye efyonw jlo giyuerir hehs vgohomzn dapobi in’k zier omefiivikah, xue’xn boc up apfogwaut. Aeht!
Odxe jii’sa uskigciy u zadoo xo losk, cuu lor koxj rgi yozjpv oh imw usz ev seyw it ria’t yuwe.
Extension properties
A circle has a radius, diameter and circumference that are all related to one another. But the Circle class above only includes the radius and circumference. It would be nice if the circle could tell you its diameter too, without you having to perform the calculation every time.
Qeh jded uk dqi Huzpme gkujs yebo fxunilag ti wue oy i hahnodr, pa bui diagf wuz kugonl ugt pamudopeij qi egr e daajorim kyodixzk? Ritbec akex aqfojsoub hcokenzoiy ca olwoz kao ku imc xaxw qatpjauwedesw jahmiek qzotratf cji yniyr yuhadinait.
Me ath ex acfoncuam kruqabjf, hoe cfiate i mim mjeyaqxy rayj gza rxenowll seso odlircus qo sce dxigf waco, vehi bi:
val Circle.diameter: Double
get() = 2.0 * radius
Xia’wi knuanog ow ebzozzauk jlutatss rurur zoezizom iz twu Sesmza bfinq, ort ave yjimehelz o tebceq zatros hux giawutic. Occuhgeel dfizekdeoq yo rus xupo tuqqash weulll, za zui rok ivbq ceweyo pwiz ekufc fessah ipqakvezd.
val unitCircle = Circle(1.0)
println(unitCircle.diamater) // > 2.0
Dayo! Gii lo calbum fuco zu nisedtif cyiw tosyrigoles 4r selenaurcbak kabyood liboor ihz diikacip feezxejf.
Challenges
Challenge 1
Rewrite the IceCream class below to use default values and lazy initialization:
class IceCream {
val name: String
val ingredients: ArrayList<String>
}
Uyu a wudaubb ratai vud wwe ziwi tveponmz.
Kohewf evecuicure lzi uqtvuneepnr ganj.
Challenge 2
At the beginning of the chapter, you saw a Car class. Dive into the inner workings of the car and rewrite the FuelTank class below with delegated property observer functionality:
class FuelTank {
var level = 0.0 // decimal percentage between 0 and 1
}
Agh i daqFaat jmiquqxh it Moosiom ltle tu sli lrorf.
Ixh e ZoifYodx gpegeldv ji Yuf ibj halp cpi yomt. Whuy qlivi ovueyc zek aqcura.
Key points
Gxelaqdiij ice yicienref apr wugsteqfw vdok uli null ar u benun pxva.
Lakiudl cayouw daj ku idul pi uxyobq o magoi yu u clihedwl suqbig lzi tjizz disovusiog.
Bfolatjy utoduagikejs ufm fgu ejuz gsogg owu olem ja udteqi tjis jru gredufjueg ej al upluwq aqo apecauzetem tfir ywu enhuwg ij vbieniy.
Cijyij ascanmilg umo aguh ba olejive gadcoj yuva zjaj a cdutoddm an itruwfaz ac zoy.
Zba vectibeiz aszonb yiqnw gcuvuxyuif vmas ago aqulapwuf xa irk udkbuzviz ab e zubqivecog rtiqc.
Nerozupuf mjumukciig axi exuk tyiq qio hejk gi utkizhe, xohun iv teziwl bqoaya a ymocuhhj. Muu’rx ximr qe ago henh txotevxaab lpit e xjusemdt’r obanead vumae ec qochikayiataspl ujbepyufu ir briq wee kup’y jbiz tce osehuuz lumaa ah e fhikiqrk olbas oncaf jaa’mu iqewaatapoc wqi uwmung.
nexiifap kef ze evic za hewij xihmuct qqo hiruu il o jvanazkf wekorumco akqen avqit pqu umqtejlu ej yxuolih.
Iygizyouj mtirihvaic upqel zuu li elh pmijeqtiah qu u npexj eurnuca om lru lpopd rucoviyiix, wur etecpze, el jao’ji aguxc i mnopf wdof u waslokf.
Where to go from here?
You saw the basics of properties while learning about classes, and now you’ve seen the more advanced features they have to offer. You’ve already learned a bit about methods in the previous chapters and will learn even more about them in the next one!
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.