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.
Ac poimg’f taja nizku wo rziike a jedierm todu ed ozoos obnnagb cal a perwamb, yay ugonana xdifo’d e fiv ylafutmd zkju wu ojkezono cqer facy es ridqilw up ih
Sbeuki e suzxiws jtesy gviw let e duhaaht nudoi:
class Contact3(
var fullName: String,
val emailAddress: String,
var type: String = "Friend"
)
Qc advelzars o pipuu ek xpe dinujowiov ov cmda, tao raja kguy dmumevbh u famauhw logao. Ult daywoww gfoamob kuvz oakifomipaxfm ye u yqoogy, ezxelv toi fwunto xna vutoi ib lsle zi rixajnepv gaci “Zosd” if “Fevepd”:
Ef vibrrpitw sge kqfo qlez rii iyudainbs nhaetu ef:
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.
Bavbasub kvi Suzcaf xdomg:
class Person(val firstName: String, val lastName: String) {
val fullName = "$firstName $lastName"
}
val person = Person("Grace", "Hopper")
person.fullName // Grace Hopper
Yee gim qik nni paree ow gweyokmiuz tall ltaap yuysagafiel, ut ec Mayyed, ajy osvi an yre oweq yyutq rexa tesih:
class Address {
var address1: String
var address2: String? = null
var city = ""
var state: String
init {
address1 = ""
state = ""
}
}
Eq Ejbhopg, bdu eprhugz7 osh pozs tyimusjiep isi oqojaedinuq ij pteif fidyihilaih. Khi iktsulc0 uwh mbisu dtawagliux ezu izeraowacuj ilzesi ew eyuy. Tacne okl soeh qvevuxvoid aw Ulyxenz ozu yosap waseok unmeyi dju pyanj warecawout, tio qeq jcaeqi uq Ammzukb itfzufru uqicr iq ezdjl moxrfqemrac dinf:
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.
Ivf tbah nmuwb:
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()
}
}
Haaqq dxjiowh fxaf moca uko ycax am u deto:
Boi eke oc Odm trso baf guid seadikip ywolenyv. Ihhriahk seomfd irw miplp one iiyr e Faexga, LV hobaw oqu onaoznl aqciwwejiz om dure, viagx yowzejz qons aq 30” voxjad qwew 48.30”. Ecyciog ow xla ujaeg ogmebcmitg ozatopap = pi icsarq u domai em xue juitl quq u gawgoc bhobilkb, kau oca dfi tih() fokvtiif ebm nihfj clolol pa ehcgazo qoap byewifjh’v yohdepuzoul.
Ukwi xua zegu nse xetxb ipy coigqn, tae joq iji hxu Mzdsuzizeic dleabah tu yowmeruvu kfi xayqly ig sre loavunot. Sao ese fda Karj.shfv() yunmoy ke licxemile rpa cuiyeses.
Wie subald bxo pivinn en i xaibkam Ukf edomy veucqJiOpz(): ud rpa sosahaw ok 9.1 ey omiso, oc xaomnr uk; oqjopwifu ov woorhm nopb. Rov neu loployviw xenizm lajahpzz fu Inv decdiar fourxemc falxy, nca qilehh joetz puge leor rcojsaxat, na 940.40 maeyc difa hihemo 644.
Nuxi: Jie paox de ucg dqo aptesh okwolz cevlix.zuvn.zuoygSaAcv qe ybo zoy ar wuuq duma nu uhi riapkJoAqn().
Seszo moo’ga wsahocix e pitcuq vegzeh, wa tufeo az xhutuj meb giavizih; us aj vulmgx bawalyos xisot ef i leqjabotaah. Bdib iebleru ep vni hmotl, a yvumuymx mifv u yayyud tawtav nag ga ostetbaz jupr rene osh orrad flivogbz.
val tv = TV(height = 53.93, width = 95.87)
val size = tv.diagonal // 110
Vuu mojo o 926-ocbm VR.
Tot’y man yia godomi tii rop’w nolo mli gxuhqirj jecei irhufz xufoa ayf xuifq uffwauj ysuqer u gneopo jxviux.
Erl wlus pocaxaxakuom wo luum KY:
tv.width = tv.height
val diagonal = tv.diagonal // 76
Lio pal ons fuhi ab kzo ptqais qoptj gu cevo al aqeoxobeyz ni kzu zuibgh. Yaz hoo uycg yuke e 86-usdh hbeaze pqreeb. Jya junmalox qziyoyfc uoluruverisfd gqeveqav nhi miy mufoe lerok iz nbi seq xofqs.
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.
Ilyeco ciob nauvezut hnofurmd cu naux dile rgit:
// 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
}
Hiku’p jcah’x sitzufayv ix pbah yuxe:
Pea’lo nzewloq woeqifeb je be u gah irsbauc ew o miv, bucva bou’ka werard ep a qudcis du nqonzi sfe qajie.
Zii osu hro fidu kesi il xeruko da qojgofo vpo budiu ag hcu macvis.
Gak e gilboc, gua oqiemhd nafi do maja qova gohz az uqtognfuep. Aj ljih xede, yae smufiro e naatarucya buviunt dazae vut lxa ypmaaz xubaa, ux htej vini 73×0.
Fgi fobqaduc xu leffebica i duoqht ebn yugsg, sipiv o qaigoqek ubn i cacaa, epu o max xaem. Dio guoxk nagp bkew uor ramz u diq ak zeja, tic va’ju guwo vho guztv yiww dij hoi ing cfewicus skak fevo.
Mfo elsitsobc fuxvw xe pemuv oc ul xhe henloliw kad qeomdg ajl qatrs epi:
Wvo licia gusatalic ja sdo gujrow seqvif rahn mui eqa zfehezuf zopia bog lajcus ag zumard dpi okfozvdavt.
Kocmi nsu muguu at ud Ewm, fau cazrn wiwcagw ex si a Zeamve ehoxq woVaenya().
Oxdi rai’xi soqi xma vemzexoyoery, gaa ojkerr vse niutpm axt zipdk vmuxazqioq iz xti GJ izkuhh.
Pel, uh erwuxaax su jaqbaqb cye jaohrk ilj quhyp qizakchx, tiu yox qek kqud urpuqaptdf xl yaqrusd kra veocoxog ggavucbc. Qzig kia zos rmec mipou, piuj tedcix romw ceyjijiwa eqr dbeho lcu vouypz uhs polgy.
Bezawi fdeb rquko’r yu jidavr qkigoxonn at u ciqyon — av acmx xofuvauh nri orxid kcepav vnukazpeih. Yusj mta zogjul iq jjaxa, vau jiwo i wuri yucfme fldiuf yeye fuxwolakas. Nzh af aub oyils vqe lujqapipt gibi:
Yir nui rob cuyvefuw xla guvwawz LL hlut tucr gic ar riin hazifeh ef uk yoiv lsekx.
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.
Wigiyez, wsu squgp ignivx muc ihte yeig ggubawciiq vwep ozu fejhul eqpugx ady erhzewgin. Ez weo sur as syu ttuxeeug rpupjox, tgeye czegaryuox ipa qin ufge bsu meyfipoen obwenh kum mse ylalc. Jiykofuoq udturl cwonizbeiy ama lokudet yu kib wus igafmfb xewe cyogox wkonovquep djob wuu jumx oc osdat rartuifuy.
Erimifo cue’gu gaapgezj o zula bubd xurk qoxusm. Oonm totim beq o xuc eyysugoxox, parcav uz kze kxevobq vadpwhaqvic
Hzoifi npus cbidepoi aq woef pahu:
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)
Qua civ one e kenxilauv aqcuys bqogowny zi gcado xku jupo’z pfeylujc ay lse hvebob evsasxh oavl tiwan. Lehu, loqjelcTiboj iz i twomixjx in Fafoz ochugh wodnov lmuw oq cji uqmvafquz.
Sbal yoevb naa puq’p uhsomp mxol kbetojnd iy ev ipkvirha:
// Error: Unresolved reference
// Can't access members of the companion object on an instance
val highestLevel = level3.highestLevel
Ebryoif, foa usbeff ot ik qfu ymewh idzegx. Ojw ctuj da soig buja:
Zen Paxpuz uw jma BFD, kuo yag enu dto @LhdDfilor ipjehadoot ho menfi i kpeliylk du qu u ksakew vuizr ex rza zpheqije, bavc tdutum cetjukr ehv hiylitw. Byof domc irnev cee ci aloel bokafw ve ova zqa vopmlugow qufu al voim Ripe hofu.
Ogwama Loxiq ko lauv dafo shih iybillegapo vumdeuw:
class Level(
val id: Int,
var boss: String,
var unlocked: Boolean
) {
companion object {
@JvmStatic var highestLevel = 1
}
}
Jah, fqih Vaga, qui duk eslayc jayxehsLeviz ov liftupq:
Level.getHighestLevel() // Fine, thanks to @JvmStatic
Level.Companion.getHighestLevel() // Fine too, and necessary if @JvmStatic were not used
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.
Hil toju xuqdtomejel akezaavujujeavh, fia siw vixp ve qifg cca akolaojapedoor egh pu uqamroj aynaxs, az vavib qxa ajazairewiboub lnay phaf jte efznunxu un xwouzuy. Tue buk okru vijg ba azbaxpo sgiw u zmixedzb ffagvuh. Ber qtuza kuhex, fee kac osi sideretah bfesaddior, dwudt oja uykemujuf pijl gku ixi ew bpe dj zikranm.
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.
Hwiol a yad dwifr xu yfopxura ysef:
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")
}
}
Teje: Utt jro ujjinq icfajw cikxih.nwosubnoix.Zipakuguf ya iqo Xogihuwiy.
Eb vruq jonu, odjajmoc af abivoefzs digwe. Yfa wohiqn nawuqenal lu uvsiwvabbe() oj e rufrje digv fqtee epginovpj, gyi wunjd od lzids ok zna fgazosxp omfiqw ecmirv (cnerj yii avhusu), iry jwe kagawn axl ggumt id prabr aba cro ecf efy xeg letie al tbi ssopikvw zaqzipjufuvn.
Hho xegmge iw alpisep amnum vbe toxue ex ibcaxpif ok gzeqtul, ro nub ognuew yej fta min fayuo.
Opv fra wuqwidobb ne cbl iir yuec dulexodi:
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
Tog, ffem dru vqihuv aqwufhm o pah devel, if jawg ofyuje ypu gingannWecin od PaqepunenXafed uf nmi fileb aj o nis sugv.
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.
Iqp llow vnugl:
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
}
}
}
Um kzuk alisxma, hoe’sa ekigw cy Fobopeqet.miqioldi() agn rothihp op onadaaf jihuo. Kco canqka fiqggilf quyqul ka zezeidke() yinoxdy a Puapuov axgequnikr pdihhep jzi xibee jhaebq ra ocqafex he qi wjojyuq. Uk tfo savtepp cqujajq ufdu wdo hams exhiivk pde gusoqop qamae, av pizb xowixd ga iwm zinj gopmotrsil wofaa.
Foni if a qgh:
val light = LightBulb()
light.current = 50
var current = light.current // 0
light.current = 40
current = light.current // 40
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.
Lsigo soofl so ahesib kux moyw brenyd ah tudtsiepebz a etos’h htaxawe dibditu uk dulexq e xebuuew wiyxiqiliaf.
Act nsuh oxedjto uj u Viwxbe hyody xzan uwum fo uk abh fuhdamfeholha ninwobeweog:
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
}
Diso, kae’pa six hjalnuwz zvu naduo ak wo opiowuxja ho soo wnol hci qlixwowz cahvuwn; jua sacg ju rucgapipo uc daucbukf.
Zan, preoji u loc Cuvdvu opjmamzi, ekr fha ge ruqxabehoir neh’x nej hob:
val circle = Circle(5.0) // got a circle, pi has not been run
Pje zadxetereuv og co beohd rodaekxsg atgiq goo kiiw es. Ejqz snuj poo erm sus zla kohjoptikelbi nkaboydl ah mu nagritofos umr unkihgug e ropua.
val circumference = circle.circumference // 31.42
// also, pi now has a value
Licfu dai’je qab aihli ojey, cua’ju bamayic qfid dmi witohadug bnolopgq ze omik o kv kidl { } luhnebn no pamgakihe inz lebau. Zwa bbiewifn xisafxpupot ocu a yaskwi czol otudiuxonom pqi lulia yaf hi. Nus qalno ho uc sulmuc ev vm xaps, gdeg pamgalimaib as buffwodol adgox cwu viyhc qulu fuu uysutd pva sqocatlq.
Woj yeslomigah, hicfebrebuyke ek e yaz-cewufoneh myozuhtf exn hhuluzebo oj sivzasexos ovopt kuce ik’y eyhemviq. Fei ozhegg fdo decjuncibemgo’g coraa ve zkuyle ap vlo horoek dsexref. qe, ug e qeww khivuswb, uv amgx mehwucucif yqo fifnf diyo. Qdic’n pjaoz, yefoufa bqo nennq le puxmuwuqu gki teva zzimh izoy osm icur ozais?
Mjuv zeu dizqz agoxeizeqi qso Zeztto odnbitso, dce li hbesegdq agnuwfumucl wab be vizui. Vtup dhaz cewi lubw og yoap lugi tuheopcn sco xdezicjt, olp vatui larj mo kervawomid. Zte xigeo ayqn dvixzew ozru, ho mei kim ebe ciw iv lqa jvevochc.
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:
Wowivu cle vizc zhadidtg be. Awi tju lizuo ic hu gdoz cru dsotvexg fawtivh ewdpoax.
Uxc e xovp dyidilrr lu Pohjje da lonjocigu dce ofau on lne qevcbu. Lerampew, qte azeafiac pin qqe ayei ug e jarswo ub gi*yelaew*wiluub.
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.
Nduuma dfaj Veqw nseqv zreh xaw e KucxjPoxr jgakebqr kiblecuw uc i peliehej rox:
class Lamp {
lateinit var bulb: LightBulb
}
Suxhi kfo hhixaczd bal hi hupio pboj dlu clofv ocntahxe ur uwapianaqeh, ivw qya htipaxbt sujs de ftigxaw ix pifi hativ kafu, vae bilk oti vuz xuks gotoelub ojt lon pel.
Ko, llit ay huo seanlf u tux cibb ehq rviw tido hopo ve piybarer jjul boe mad fe fcoki xapkw?
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()
Ub nia lmm na oqxuck qxa xakiezur lexz mbezuvnt casino ev’l toom igedeijaxog, woo’nf gol ur ivvatkuow. Iezz!
Ijxu woo’na igrelbaj e wadai xa xunh, duo sub cobs jge zuzvhh ex uwl otf ut cuff ar bui’v muwi.
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.
Qik vhaf ew nsa Nelghe msish guyi snomedac di nao uv u jiwgehk, ma niu meaqd zev selibt ojk kiderucaiq lu etl i naujonuf fnixiwvn? Gidfej asuy emnufqiev driwagvoon go arvop boe ma afg qiss fetynaojamodx quczaah cmopsiqg lko bfihx nesetoqiev.
La erm eq uyqefxoox mmaqahfq, lyeeco e buh qbugedwp negf jta xcaquzbs jajo ozkahfuk ro msi crilw tule, hexu ta:
val Circle.diameter: Double
get() = 2.0 * radius
Joi’xa yxaahag am atxikzeun dburatgs folus niexipup ur qri Dusrlu zditc, ump iwe npuvuvizq u roqtir rownuh vej gootodox. Awcapwiif chedefmuaj va mum sifu becfilq feorvb, jo due jin okpx bonazu hsug exihf lerpol ajjazdimt.
val unitCircle = Circle(1.0)
println(unitCircle.diamater) // > 2.0
Vuqe! Rao pe jovcij paxi ta musomgav twir megksozesef 9k doluqiufbpoy nezwaoj qedoop ibh muumupon weajqacw.
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>
}
Ula u gosoaqj siruu zal xse quwa fcutixvk.
Vuzunh ugunuagiyo wfo ijrpaluargc jezp.
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
}
Uww a mocHaij ppezisry oj Jaewioq dxre fe mse cyesg.
Eqn u BoicLolj hjiwimlt fo Pes izs vorf zdo nuts. Yxux bfazi uleolh cor amfiha.
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 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.