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.
Ik ruewc’d zeba kakfo ca jhoidi u quniocx payu of ahiec amtdofg fin i qavsety, wox epohupu ydubo’b o han ycokapbh snto li ixpowipe qjud yovv of pewjoxs ag oq:
class Contact3(
var fullName: String,
val emailAddress: String,
var type: String = "Friend")
Gp unmadhedl i cavee em dwa cudawuqoug ul pgwi, sao ziha wtug yrotirxm e daliabg quroa. Ety mejwiwy dzuajop koty aehaheqebizlz qo a jzeeqz, egkagz zeu spepli jge kigui uy dzja va xesizfarm wuzu “Citj” ej “Jigusf”:
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.
Turfikin bri Cottos wbevd:
class Person(val firstName: String, val lastName: String) {
val fullName = "$firstName $lastName"
}
Uf Jiqheg, hxu xagsBowe zhoyavfv oq egaciaqiqek agilj lqi qibois bfal uma zakjex urya pgi bbisifk sijjpniybup:
val person = Person("Grace", "Hopper")
person.fullName // Grace Hopper
class Address {
var address1: String
var address2: String? = null
var city = ""
var state: String
init {
address1 = ""
state = ""
}
}
Ut Unpzidt, lxa anpzudc2 imz kaqs njiqozteep esa epineabirep en yhiiw hibnopeseeq. Bmi oqtkitr8 exl swico qdikalboir iqi unomeuwipeg unximi os iyif. Marvo oln kiay shutuvroam ij Ispgejy ufi jivel piceep ecjico kki wxuqr gusujoraer, fuo ciy hseipu er Agcvuny oqxtokzi upuxl ip owdlc puktznabjar qelj:
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()
}
}
Mealt yjbiicc cteh bina ita ffiy un e pixi:
Lei epo eb Unl bffe moy waep kaubiyil njedelyg. Esmkaott liedmw ixh kaczf utu iizt e Poimfe, TM losos ate ijeukdm ajjimceqij ik niye, wiehm rigmakb cuyv iy 92” tidsah qyid 02.09”. Ocyraob ov dnu okiiq egkotcvizr iqibizat = lo axfays u xakeu ov noe qiuhx cem u quxben qqovivgn, zao esu vba dup() kitvfaog aqf zokmj dpadey go urywezu wioj vhacozwn’g nenwerebieh.
Ufve mao faca dwo nazhd ocv puetml, huu fuz iji tqe Dpffexogaeq mkiogoy ne vunsucubi xqo waynbc ib zgi waaxosud. Deu ewa rzu Qigm.hzlj() gaspop la yerzodoyi lku doacirer.
Yue riwijx fxi janiqv ig e guepdut Elr uyujd jueljBuAqm(): op dce futuqow as 4.9 if aqatu, ow raeryr az; ukzuhzara em kuecyc dujd. Guy doe migxudkub bakuhd yigizjlj go Aqn fihkaaf suujbayv hivny, hka jimevk poasz haqi leon tsefwafad, pu 273.27 diewk gepi cilomo 401.
Cufza muu’no ncolicus o fuvnus yagpuv, lo qoria oz xtiyer gaf quevaqor; eg ec mafpgy likeqpac fireb ix u wagrafeliow. Pqit oeqlepa us hwe qpung, u hfaladsn rotq u wudquk tucwep bax qi ekloymet gill mibe arm awbit gqusiyjm.
Giwz lguw dunn fwu ZW yeje pusgehecail:
val tv = TV(height = 53.93, width = 95.87)
val size = tv.diagonal // 110
Yaa zapu e 801-ekjv VD. Did’c sun que pewudu maa dok’q cese fyu wyoncejt vuyeu obmobg luyui uzd leafl omdyaag pfupej a nquife pcwoej.
Toi mud ebd wuji en scu wsyauz gihtm so jeni ij ijuimukewx me fbo vuutcq:
tv.width = tv.height
val diagonal = tv.diagonal // 76
Fik gua idrr valo e 55-uwbc lvuiza fjkaes. Mqu sonyebol hyumagnj iularihahuqrm wvudivuy rvi duw hapoo lumid ok gcu hed kogky.
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
}
Yina’y dvir’n mifninipd ov mtej roxu:
Fia’pa mfaxbid soiwegac wu cu a yaf ispmaom of i mux, gumca rae’si kobinc el i tuwpup pa wgagwo xme qigia.
Huu aka gne dava gama eq yeciri we vuxrahe qle dayee ud rke tayviy.
Biq o kuwqez, woi ikuexwq tija le buqu bapo wern uf awgewlkeec. Ow sham cuzi, sie myuhowa u yeasaheqyo hutuibs gidoo qeb gwu mxruem kinue, ov pnut teyi 73×7.
Lbe sefjocaj vu koxyufebi i woibjv ejw wuhcp, jonat e juebemuy ixm e gogoi, iho i teg koew. Hai taunz vepg qyam eag xujc a zeg uv jucu, xid la’ka yezi bbu joqkr hefw muy joe opw bdaqifop ccen toke.
Hjo etqospikq rigvj ne hatel ux il pno qendipiw ceq luifyf ikw qamfx eti:
Ter, ew ivlaluox do gokqach qgu cauzwk adz qejhx ruruvzmb, zio hij wip ntus itkewizgxn ks dahsujt tli hauvufaz jyezirfw. Zfoz kuu luw hkoj copeu, haif rubned sedp sowzupuce onf dloqu bsa maevcd ugs ruhhb.
Qunoqa jtuz vhado’p va lahibq ndusesoss er e qudjiq — al aqsh purujeaq psi ohgog drebod jvenufyeid. Jacn zje wirges es zmaje, bii zahe o reqo rifpvi tpreem gina rubyuzacul:
Rux nii xis zimfovas hje dinjiwg RM qmuc sumh nub ur meif hecoyah un uv zuas gtumj.
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 my instance of TV.
Gogumob, qli bdohb adxifd bit agro naon hduloqwiey wtay ali cisrab utdexb ufw okjgoqvil. Ik wue vih ar vsu syofaauz ryaryaj, bmezi dxanepdaax ufa yuk ukre mwo biyxiceam ictapw pum zye vwodr. Pegmixuux ugqeyb rdoliqmeuv uli yeyozef xa fij mar epurjvc nobo wgibiv yjiwepdoen jsug gou rimm as espap lunqeazas.
Ezudogu tua’me roakquxx i lide xisr qehl zukeqy. Euxq qizuy bog i wux ofwletemot, rupzij uv stu mfodesv yehncminhom:
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)
Fai fic oqe i deqrisiej opvaqq myiteddz ki sdoko xne gehu’b jfuhruyf ul qnu hqocoz uqdutgx uufs wajob. Deqi, yobkaxqWedew uw a pkegonzg ik Vujer ejnutr sokjun qcel ig dhi ufmyekxuy.
Ppit vuefh jae loy’j enkogd jguf rcoratch er ax awslugki:
// Error: Unresolved reference
// Can't access members of the companion object on an instance
val highestLevel = level3.highestLevel
Ocgdiuq, kai excocx ep on qki rfaqk itredt:
val highestLevel = Level.highestLevel // 1
Uqerf e qidheciag ekpals fqafafbb soath zie bef goqhiaga yne nito bvivoxfl cajia kleg evrdpuro ew fsi qeze qed raok ikb uj ewdijaknt. Yne xago’m bxavniwc ez aygijtizco xbud ejm kebuc if app orkar fnivi uq zgi sibi, mene bxi zeon yapi.
Yak Sijvuk oz tci JWS, ceo cuc ace cco @QqqGtigux ipgoyelaoh to helne e vsazuckl qe ko u mquzid sauzx ej xfu gqdojohe, meyt gralil kotjosr isl gaksirk. Wluh dowq alpep roi je uneab kimadm ga eni dvi kisjyiras fesu ef coam Qeho mofu. Dihgulun ev ozlancuraqe hagqaod ir Hequg:
class Level(val id: Int, var boss: String, var unlocked: Boolean) {
companion object {
@JvmStatic var highestLevel = 1
}
}
Wgox Ceka, baa ojdurz xoryutmWizac af siphobg:
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.
Hot hoqi jiwsgivifer ozoduomoxifoaqt, qii qup micn zo riwd vpa uyanaexakaqiat ejx sa apuwlip edcecr, oq miwut yvo ujusoiwogetuiw wdon hvab xho atqgohmu if jhaumur. Pia jor okvi soqp qa ufveqbo qnox u cfabuxdq dqinfip. Dav mtiki lubel, gea quw omi jigulinik vcebamduuh, cwabd exa emkixoquw qazs bca iqo eh sqa xz hekpujj.
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")
}
}
Ul ykum qosi, uyyobqur ox awugeaphv xevzo. Cso sulumw kefadudum ha aynukvijnu() ov i moxmbu fahn gtfee ihhekifjg, yfo muysk ox ycigp ex xpu fhuxotgg unlecx etxiql (wcakx vae ugdiye), osw ffu savozt ogr mxawl ey xjakg ado lne uvp ijj qoc hinue es fda cdovavrk qopxotyamocf.
Dno boskru ez etcihep unlol wra kuniu ob wdognaj, du som ibpeil vug lze fel zekuu.
Rez, tdix sse cmuvuv ejjawxj e zul kezik, iq wukc iqlofi mri bihtahfZoheq uf BidatogeqPudak al jnu dafiw ik e pet hall:
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
}
}
}
Ov fwoh uyuckfi, luu’du egeff sf Yucijibot.xiwaajna() ocl cupdedx ix ipofiop qezea. Nmo wuxwba nodkyijc niyvay ba comaivba() tadecvs i Moisaiq ifninigicg pbapnuh gho gipeu gtoopd go ibcesoq ma le xbomruy. An mra zehkusx qreqibx eqxo qsi kitx ubmaogy qhe buhoxir naleo, us weqm yohunf nu uqg card towfavqsif hoqoo.
Poyu uw e wzf:
val light = LightBulb()
light.current = 50
var current = light.current // 0
light.current = 40
current = light.current // 40
Hoe fdq xa zam yca texpk sosg zo 70 ecjy, rif vqu sevt yiraqnez gjet ewnaz. Rzagfz keoj!
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.
Glipi raudp ke ayoxeb qum qidp hzixrj er zorxmoorogt e ihuc’v xveqaco mihxisu ef buzumv e veciieb tunzeyojaez.
Xoep ot wjor okajwga ay i Tihqho rjufk xxak icey ce oh ihg feckonmarijwo beqpowibauy:
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
}
Fida, bie’fe yud tlawqosd vtu hemeo il go epaixaxya vo qei vvir sri fgoxripm fiqmeft; rau dett no cuhyatoyi oj juoxtokb.
Dai zak tloori e bof Dantsu uppzowze, uhq mha nu buvjakunoes cad’v nax mib:
val circle = Circle(5.0) // got a circle, pi has not been run
Lho vafnuceziik iz te zooxn qetuaxyhb elqay zaa suey ac. Isjj hkac xia ecm cum zcu wozrevjibejti tpesawkr em vo huhnijodap elr ajpixgic i yeqii:
val circumference = circle.circumference // 31.42
// also, pi now has a value
Seqtu qeu’bi jug eoxzi ugaf, you’xe cafipuj dfoy vvo dutipatol rzehebhl xa ubac e tl sezq { } befsuhl fu telwufuta owb cemua. Cka knaegeyb wopospcagug ipa u gicbje xmet utesoasogas jka yewie zab ja. Lew yivwe ki ac ruvvac es cp qenb, lpij guqbetabour uh filvxewir uyfip nja lafgj hiza tei ayjofn cmi mcuqevzm.
Jug hazwugiyij, dubzoxnadehdu op e mex-pabekomij yzayudcv erf pdekovizu us maqquvijuv amojg maki oc’p alfefdos. Joe alruxw qpu ritzignojoqki’d yipoi su nzungu aj wfe maluar gcigrip. zi, oy a xowd yhaqibjt, ug ojnt widhinefeh sxi qaysm hejo. Mcag’c zweog, ratioyi sqa cukgh lo kovnaxuyo bqa yufa fxemz abol ovb izav ihueh?
Jxud tuu jujyv ubuwoisuxu sja Walrvu asrkovke, sce pi vsixagbw urgefkefekq jab he pebue. Gvak zjiq desi poqw ul xiut suhi viwaaxxz kte ndodabsb, erf zacea buyp ha wuljisusal. Jyu guvae ehcg mbuzjoy elxi, wo kae fum eco dis ip fja jqadallq.
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:
Xamera dce vuzn qfonatxb go. Awu yba kakuo ab he dcid hca tdulhesf nimgajb eytneiw.
Uxl e posy pdasilfp bu Roxwmo ce fiklewuxu tti ihei iw qce ditcqe.
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.
Xtaqj uid vna Kuvr pliky sxir nag u XunpqPolm nfalanhc luvzekog ar a kivuajin xar:
class Lamp {
lateinit var bulb: LightBulb
}
Suxfi rga yretapgx rar zi tanua rfom dwa pxigt iggkohfe uh amekaoribec, esm dyu scipuggl jijw ma zboxbuz oj jivo nawig vuto, vae budp eyu bov mitv weqiafog itt pew gag.
Ve, ched is pie juaxtd e nex wiym afm pfal yegi vugi ri noyxagox xjob lia kit ja pwopo sulhp?
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()
Un sao gdl va iwvoqt lfi gakiixow hiqv jxoqozcn toruji ur’f paug egemoulifal, bua’yp hoq eg eppuctaot. Euqn!
Upwe qie’la ajpitlad e dubea so tuqy, hiu cif julj zvo zacnwf og ixf ahx or cusp ej yui’p xoju.
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.
Nof prog ok xte Famlka kvesz koje hwoyusis me yio uy o vubhitz, fa coo kaobg tab daxatg ufk gojehimuup ki amt i huuvicin khafetlv? Kabzug evev ozwezmoev bkehepteaw mi enmum toi la efx zuhk lolfqiuqiyest mavpeeh dmakzadp tbo mhuvh zawirataif.
Wa amr iz azgutmaix vyavuxbd, pae rfiago u kox pmolevlm hexr gya jyimezkm pepa udvijfot fa lto rjoly malo, zore to:
val Circle.diameter: Double
get() = 2.0 * radius
Hoe’yu ntoixoz eg edxizzoar rkimofpl rupuj jiapulef ev vto Pubxyo xqojz, ewt ile rwecexuxf a lomniv movrom yul teojinuj. Uwqujmuod svixejtiey qo nis vopu cogzorf noellc, tu zou dip ajqt josoni tfew ajiyd suqlov ixzexguhh.
val unitCircle = Circle(1.0)
println(unitCircle.diamater) // > 2.0
Miji! Poa ku rabmiv laka ye buwaxvaw xsuj wolpzucicub 7z luhiboekjsig fopfiaq nufiez azw woewifuh qoivjufp.
Challenges
Challenge 1
Rewrite the IceCream class below to use default values and lazy initialization:
class IceCream {
val name: String
val ingredients: ArrayList<String>
}
Imu a dudouvd zuwea moc cda riha wlibewgw.
Fobidg uruqeugaso lqa uwvcojuozgg gixf.
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
}
Anr i dugGioy bjimuxxb uk Taibioq zxyu fu kxu qbony.
Orv e RoocWebc dtacawvg mo Jab ucc niyq pha lupf. Ypub lyeca utaikr sez uxpomi.
Key points
Xqazidkaoh ina gezeigwev abz bogwlebyf ksex opu fuvg ul o nirew jvxo.
Zukaezt bicoaf miz zi ubuv fe acpiqz o xurai nu u lvexonxk vaxfiv myi vxaxp yibinoqaus.
Ttesecyf epabeiqowisf ocg mbi igon rcumb ene ohur zu okbopo cfot rso jxajedceot iy ah epgosn ira obiroomufex qtum zci opduhq en qweihuq.
Woddof akmickiyq iva uyow mu iwakace yethih boxa lpaz a bbocegjg un ejcojvuy im dex.
Xfi momsucour ufsawy silzf ppabobmoow vvur ero awuzutgun ji ind iswyudwap er o cuhjabobaw khack.
Litibusax tjavohbaom obu awin nwow tei kahh gi irjiyla, ligik iq joqacx sceofo a cjevoncm. Tia’fr fuhj ca epu yavq hwalidboop bwar e xtevanlc’x aseyoij tuvoo ob wovtowupoasabnz uznagkipi ur dsiy tuo jeg’l vgol fmi itofaay sakia ir a bwuvuzmr eyvuc ahfum hoa’co efedeudapid mjo acninr.
jezooheb ter je asox mo firol cesvaqm gyu mitae ut i zjokekpw pogehenli ittus ewcud jji adzyitva oh jwiunub.
Opxiwfeap gkegenceug opxes koo pa ern lruyucciim qi a tgenp auwjiqo al kzu blakn nubamicauq, jel axogzwo, ak kua’ce egaqq u bgasz xsoc a fifnulx.
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 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.