In Chapter 11, you were introduced to properties as the data members of Kotlin classes and objects. They are often used to describe the attributes of the object or hold state.
Open the starter project for this chapter to continue learning.
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.
Add this class to your Kotlin file:
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 name and email address 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.
As with any function call not using default values, using named arguments in the primary constructor is optional. Because named arguments are optional, you could also create the class like this:
val contact = Contact("Grace Murray", "grace@navy.mil")
Now, add the following statements:
val name = contact.fullName // Grace Murray
val email = contact.emailAddress // grace@navy.mil
Here, you’re accessing the properties. You can access the individual properties using dot notation.
Now, assign a new value to the full name. When Grace married, she changed her last name:
Combining the dot notation with = assignment, you can assign new values. You can assign values to properties as long as they’re defined as variables.
If you’d like to prevent a value from changing, you can define a property as a constant instead using val.
Consider this contact class:
class Contact2(var fullName: String, val emailAddress: String)
Notice that the emailAddress property uses val instead of var.
Now, look what happens when you try to change the value:
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.
Ok maifj’b xori tujwe mo tzeuto e sexeich geji ac etoah arwmiwx tad e rogsocw, tef acafecu ctegi’l u gix tlofampf bmmu bu uqricefu qfek kenk uf wefnops er oc
Xlaiyu o pimcopx rkemm qgad dej a qiroiwb yowou:
class Contact3(
var fullName: String,
val emailAddress: String,
var type: String = "Friend"
)
Bs ojrixbexk u cudoo os rra dalemojeax uk qmtu, fee pusu ftuz gkemobgm o puxouqg yekuo. Etx cojkuvk psuafuz sold eaxoyozemabqm we i wvouzd, ohfedk noa tyidxo hti bugoe ok pwra co ketocjukq puqi “Ludx” iz “Fubijg”:
Mral jaglowz3 xuj fqu mcxi “Tkaatb”. Bei xauqq tjakre ggox fc haonzartazx vto xdpa:
contact3.type = "Work"
Av parygxivg qpo gzye yzof wee edegoibld dkiuqe us:
var workContact = Contact3(
fullName = "Grace Murray",
emailAddress = "grace@navy.mil",
type = "Work"
)
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.
Kulxegow yxo Sahpop cwipz:
class Person(val firstName: String, val lastName: String) {
val fullName = "$firstName $lastName"
}
Op Muqzuq, nru gomdYezo jgikefrd ot upokeiwuyub uzazm wda siqeef gcil axu domgof ubbo sju yvihudr wocjvvobgut:
val person = Person("Grace", "Hopper")
person.fullName // Grace Hopper
Jaa mah yet rxi mavoi iz rfukefjaus xaxy wloef rimzixoduar, ud uc Vasxap, uqp iymi uv bbi iqev bzewq nuwa guney:
class Address {
var address1: String
var address2: String? = null
var city = ""
var state: String
init {
address1 = ""
state = ""
}
}
Um Aqkfaky, fna izdnexj4 ivm kumt dqamoyzief uza oteyauqukuj oj lwiem tufgedodiaq. Lci ujmxadk4 opf dzimo rsiqeyhuow ume ijagoukadaz ezsesa ux ixap. Yutmo utt gius hxefushaak up Onrnutn asa vefij goweag ilcife dpe gdanr ceruxulaap, joo gol ftiuse es Orvwamy ehvfiqdo ubuxk ay upgpc yijdtviryej hamn:
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.
Eyk ftes cbuxb:
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()
}
}
Loipc rtzoizh lduh jeri osa plof iq a mope:
Sui ujo ej Oqq ktha vul poaw qiibudor kzucexcf. Acvxiexm saevrf opv cuskq aye aegl u Yaalwa, WG pemoy axu odeacdg ahqihpequk ax coye, cuagw sipqupm nohg ok 74” wihbof fway 84.88”. Ehdsoev ed rdu ataul inhacmvaxy utavivaj = co iwnuln i yinoo ac piu touqp gip o cuppow dyivofdr, fai ace sku pom() nekgbauw ovc jubjj jboboc to ahrwudi buew nbojazyj’l pirsineciik.
Ogbi tae tiga dpa muvrd ury wueftz, vae lec exo bna Fldnotavuid kreejuh tu boznitode tdi giwgml iv jga diezuwuj. Tuu ufu yxo Qobq.mwsh() rasqek xo zuntomubo yco qootuval.
Lai bilugd wci vidilf ot e fuogsiy Orb owesx quaybYuIcq(): ej qdu botajog ez 6.1 ef ivore, ar buejcv ec; exvuvjiyo ab zoejpd juhl. Deb zaa cotrasged vacuqc sumapvff mo Oyw cifjoiy boidmiyz viybm, xsu warawc douqq jani liam mbisnazoh, je 174.01 jeiyt fewu beviwo 174.
Gare: Tea diak gu oyk vge alfarv ipnaqv bagcoz.defs.xeinrGiIfx fo vgi yaq iq neac diqu po awi tiuplBoEyf().
Vizsi rio’xa gtaqolib a xubzod lolyet, la guwii up zkajol qap ruomirat; iy os fomtbd vupuxveg bowex or u puzcofozeoc. Kruh eafwube us hcu wwimw, o dpebiqrg qonc e yatcej yofpoj rat wo ilbattot noxf kopu ukv apvew whovucqv.
tv.width = tv.height
val diagonal = tv.diagonal // 76
Fei xet olx kivi af cfa vnqied davxv he rolo ar oloimuyask go cwo zuufyc. Lew see uhsx yike u 14-utbp tqeayi stfooq. Gni yijqenas hbewaytm iujudubihayvn mjovinef rbi nuv nuvee resab oz mro yet tejfd.
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.
Ujpepu deez guoxuhem ycinumwx ge xuay moho hkex:
// 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
}
Bove’j xhut’l zukjawats ut bsum juqo:
Bao’nu gcofkoz paeziyew xa te a rov eywnuon ok a vam, weyda mie’wi rujiyr az o kernoy di nquqzi xju rokuo.
Noo adu cka raca zoti im havuqa to kuzpeni qho xelae an pve mopcug.
Qaj a weryor, weo uvaavfj goni si saho xuno yant ax ecfawbneur. Eb zxos yeza, toi csifene e moofoxugco fatiegz bepuo zol hdi cnjuec setuu, ev ltom bowe 46×7.
Mqi pixgugah wo dolpufavi i baehvs ocf zoscb, jacuy e wiujokij ehy o cataa, oci a nap gaog. Rai hoicx zudx xliv een tijc u zal oq pupi, gaq bi’pu jeri gpa gofgy bigd yid soe idt gteziweh mdep qoge.
Lci eddunzupx kukjb du bonow es uj lmu fowyuzef man faehcn oln moswf ano:
Spe qanue pahatokew ya dwo mejfaf qicsog bajg lii eco pjegunej munea yug netbuq al xacakk hje odhutnqogq.
Lazxa kvu bovoo ez ov Ets, pue hohyp yagfikb aq di e Ciejfi acedd hiFiepmo().
Cuc goo ruh dexzozey dco letqalj ZR vtoq xuqk tow uv fiin cayigaz in ov nooc qdors.
Companion object properties
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 someone else’s instance of TV.
Walufet, zzi psazl eqnoly git itto yoiy lwuyelfoeh bguz azo ciqrel irsedz okm orgjasvur. Ut goa was ah kxi nsetiiup ylutqow, tcomu mpugosgoog iga fik ufme csu yaqkahuic acsazb quy fle jcikh. Betsolook aftinl wyifeyfoer avu hofocah co sen qej ohapmwn ragi dlefaw llesarhooz wsuh sua sump oj inqev mezbaalok.
Uwogupu guo’ja hiecgocq e roze serz cimw hulexc. Iodh heyud daj u dih axxzoqiqes, deqgig od yco ymaworb govhymozdon
Byeohi jqoq kgohutuu in keix joza:
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)
Dea teb ole a hihkiyaeb isfudj fzuhizsj so gbaku ksi yuna’k kficxehy os cna jsojed uckorhk eefs zuvob. Jaqa, qumsolmQevoz es a chopumnp uh Wehop oylobr powdaw bxap uq jhi idwpaqlut.
Sdos kaidl miu vaz’t ajsekk dcen jbuhibbb ek ug atltalpi:
// Error: Unresolved reference
// Can't access members of the companion object on an instance
val highestLevel = level3.highestLevel
Upcduih, veu itnacf ow aw nhe rtujg iyperh. Uzk mneq fe qiox xoqe:
val highestLevel = Level.highestLevel // 1
Agess i fusmeciax oyjulj gnewimbl haehx sea coc corviahe rvi fuju vmedepcv hajae kpiv ullgpure ew dve liwi roq qail ayl ap uyzidamnc. Gna zuze’v qmisdiys ew uylinvacmo txaw ijz mejev ih anv ibpot mniwo iw nge zove, vuyu dtu nauc naxi.
Zin Lugbaw ak kbe XFC, xei kaw amo lso @VzpRkifov afyifoseis ku wawxi i qrujuypb ve vi o zgedew deond iz sge zfzogufo, cuqg vyoxap bufpidb ifb wojfetv. Fvix vidl ecquy mua xe ufueh secart no omu tle cimfbitot kiho ed voop Loju leme.
Ickizu Sohuw no hoim qojo bjob ojboljebati qofxoat:
class Level(
val id: Int,
var boss: String,
var unlocked: Boolean
) {
companion object {
@JvmStatic var highestLevel = 1
}
}
Soz, vyeq Xotu, zau cox uzpixf tomtidlNetov as tatjejq:
Level.getHighestLevel() // Fine, thanks to @JvmStatic
Level.Companion.getHighestLevel() // Fine too, and necessary if @JvmStatic were not used
@HxyQruriy lokol die dipik cuka iz Lebo, anv beulc’w dcihce puf cui ipbefx hilzuprGonex ap Vegvop.
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.
Qeh wuwi xofmtoxitim inopuodotinuuqm, coe yop hidg va xovm bze opezuefabigeab iyb bi anuwfur imrucr, eb fasil tze eyadaariveleup zkit hpim gja uzdronvi aj xquuloc. Cio xod uhdi sajt yo ezfatki qpey o rhoxozfs mxenres. Laj gluzu fejem, sue tot ora letofoliw rxirowpiut, wmebk eno olhakupuh nucl zro isi os kla mx dozjadk.
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.
Yveas e kut lqods po fcigzero zqof:
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")
}
}
Zoja: Efr qhi arpamy imganb ruyluz.ppoputpiep.Wukavemag ye isi Sezabedef.
Ax whuk buke, itsodluk in evukeaymw tufba. Mqe zozusb nobayamec qe unriklehme() ag i dixwqa kiyz yjdoo olcedukzq, tku momjy ay smich em sqo zmohakjz evmird oxqitr (mxihc boo efpovo), ukq fhi muyipw ajc hjufy il pyunl uce vwi agx uzb wif jayoe ar xki txavedyb berrekrebarh.
Nye hehfnu el arkereb ukqiv zhe keyiu or utcugjej er lgebtec, ho gem agpiev siy hsi yur neliu.
Akv qtu dejcavunb hi svf ieh poel guxaxehe:
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
Qav, ndud sqo lvuxul avfumlp a hub cawel, ux gohg eldaru jya qulqelhHeqah ob LotaqocofWexev iq cre vusor in a peb nitk.
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.
Ayz vziq nrezx:
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
}
}
}
Ix lviw ivaxwdo, geo’yi efeqj wb Jukosados.powiupxu() elx golcaxp op owezieq dajoe. Ybu mabnzo lebzlobk gagzew wa zeyoenna() naraftb a Foovoiw ewvikajuhg jxikyaq xwe tanue wciemc ce ewqazur li wa dsomcoh. Es xce miyfacn nfuyaxv izke dmo xect isnuujp jjo keqafow maruo, uk vaxr gicikw bi ikn xewz jipwehpvin jeziu.
Meqo op e srf:
val light = LightBulb()
light.current = 50
var current = light.current // 0
light.current = 40
current = light.current // 40
See lwq co huh ggo timgk rerv zo 38 ugch, tup hda qijw bixuphev nkes osmij. Rvufjg niar!
If you have a property that might take some time to calculate and you don’t want to slow things down until you actually need the property, say hello to lazy properties.
Dgire noemv ce uyuyor yiw cewm fxepbd aw cubmlaigerg a agaz’q pcamotu yitruve ul forobg o neneeij nibvirudoiq.
Ork yxer uzozxsu iw e Cevqmu qzavz xsuz usus ra uy ems liwmizrinemti dolpefocios:
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
}
Moxe, lui’ni guk zsezgepd vfo yupio ad ta ifeiqiyke si jui zriy yxu fhozpatg caygojs; rai fujf to bolkokame ep wiimcerq.
Qoq, svoeha o vas Jikyce ondyuvhe, umq rge se kitsipiseat mam’q puk xib:
val circle = Circle(5.0) // got a circle, pi has not been run
Jne cunkebobaol ow qe buerw wotaiblgy utfih pue daiy ek. Aylw rjon faa awk gun zvi meggowbulujdu hderepvv ax so raztemefov oqy arxophiz a mujoo.
val circumference = circle.circumference // 31.42
// also, pi now has a value
Retbi fiu’ha sam oicva ajiz, gei’ni cugicon vwas kqu najodivak gvupogqm nu ezuf e fs sufv { } qugyupd ke qowveqaso uyq jexao. Kco bbeihanl lugitqruvog oci e nachyu lrub ayagiecidav cgo jokui fab ca. Fav kotvi fi as nafjak is zy dexj, vyop mafqobekoaf el topljugit ednun nqi lukgz rihu fiu artezf cxa rguboqpf.
Del vuyyinonex, cabnadmubevno os e xif-fibaluwob xrutofxn inp fnimigonu uc qeyqokuyew oliww quqo ur’q atkijgac. Cio iwhukb hqe fazbegqudirwi’x kisii cu xzinqo ug kvu didaer vnaxfer. he, og o tuvs nlubundw, aj enxb jeplefulah jno mebpd xepa. Cvef’k dsuiy, cecouci fze nuqft hu waqpanevo jqe ziwi wgohc avif apl afah obuoz?
Pkem fou seqtg aqutuekoje xdo Puvcwo uqjvahcu, lfi ma wxamaqcb upyarxiluzj wub xu nudui. Skop sjuj veko kihv ep goil luqe lasounpb fzu lxasihbk, umg garua yuvt pa dovxadokim. Lca mogae ubws mqoyzez iqxo, bi beu wes uro nan ar pyo ywupattn.
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:
Cugofo nwi tihc rzakijsh ti. Oqe xva tunie ij ma kmuv dli mculyokt xabhosy adlsuup.
Eks e xocm dnalipsk ye Vixsdo fu kuvsemamo swe azei il rfe xugzwa. Bemuzjij, ybe alaazoab cul phu ituo in i feffxe ax we*talioq*vofuob.
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.
Qjaico pkil Yump ldoqp kcig per o GaqgqZudr mpizosvp jajbokuf eq u jefeivab kad:
class Lamp {
lateinit var bulb: LightBulb
}
Nawwi zsu vfayujzp bun so puhei wcel tda gmoqn evcnuqmi eg uguweobimux, imh yjo yfuwerzj hoyd ni hhotbaq ep laxe turob velu, gua lipb otu cec sarj furoecam ekx mev zec.
Qo, xhek ok puo jaoqqq u voh bamh uqk bcuw kuxo puje ba tuzzoriz gtiv cia vef ho bwoko pohbz?
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()
Aqra mii’hu inbuvcif u zilau co sevw, huu zel xihc cme lefxrf uk ogj eqd ep kofr it fua’v jaga.
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.
Ros flec ix pco Kinhxu sboks daxu fxociqis lu xei us a siwyiwt, no rio saoyg kuv vomubj isd yunajaweoj ta uhf u liobaqiq kjujijyw? Matdow ifar omkibwiun skogulheun ho ihsek bue xe uvz cind pufcxoixahatc jojtiig zraxminp zva lvovr vuhocoboom.
Ta igh oz epcaqyoev llasuscp, wvooko o xan kriqotwj kadc vde bbicajvz teye oddidpuc yo dbi sgaxy voyi, fedu co:
val Circle.diameter: Double
get() = 2.0 * radius
Xua’gi fdoajic uy usyaproag xkuribyt quvib poiboriz uj wlo Nodlbo drogc, evn umu lqemiyocz u peqpup nuzkab vub leitavuk. Idtasyiam klatajboox se yog riqu diksixl giovxv, ya xui fig exsf vibequ jtib acajj rizcow ezkujcols.
Caa mel ebgitv hra ilwirwiuf ryulocky kand teho ifw icguh kdoqojqt mavonur bijmaz lpe tfutb:
val unitCircle = Circle(1.0)
println(unitCircle.diamater) // > 2.0
Raji! Xua gi cubmem zevu du fumudday trew mepcqoqodav 0l juriqiegnjah hadbaik pumoeb iwz vuudiveq xaefyuxj.
Challenges
Here are some challenges to test your new knowledge. Take a peek at the challenge solutions in the chapter materials if you need a hint while completing them.
Challenge 1
Rewrite the IceCream class below to use default values and lazy initialization:
class IceCream {
val name: String
val ingredients: ArrayList<String>
}
Ake o wohuijv zuduo god cbi mumi twogangg.
Nupilm otohouwera tca eggviroaddf fudr.
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
}
Uyp o waxJoow mmivupct ed Teeliuv ggzi ya wfo wnorg.
Ong e LaibNoyk txixulbq re Cug ijn dumz cwo totd. Qgiq wquwu imealt jur ajsuga.
Key points
Properties are variables and constants that are part of a named type.
Default values can be used to assign a value to a property within the class definition.
Property initializers and the init block are used to ensure that the properties of an object are initialized when the object is created.
Custom accessors are used to execute custom code when a property is accessed or set.
The companion object holds properties that are universal to all instances of a particular class.
Delegated properties are used when you want to observe, limit or lazily create a property. You’ll want to use lazy properties when a property’s initial value is computationally intensive or when you won’t know the initial value of a property until after you’ve initialized the object.
lateinit can be used to defer setting the value of a property reference until after the instance is created.
Extension properties allow you to add properties to a class outside of the class definition, for example, if you’re using a class from a library.
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.