In this chapter, you’ll get acquainted with classes, which are are named types. Classes are one of the cornerstones of object-oriented programming, a style of programming where the types have both data and behavior. In classes, data takes the form of properties and behavior is implemented using functions called methods.
Creating classes
Consider the following class definition in Kotlin:
class Person(var firstName: String, var lastName: String) {
val fullName
get() = "$firstName $lastName"
}
That’s simple enough! The keyword class is followed by the name of the class. Inside the parentheses after the class name is the primary constructor for the class, and for Person you’re indicating that there are two mutable string properties, firstName and lastName. You’ll see how to create other constructors in Chapter 16, “Advanced Classes.” Everything in the curly braces is a member of the class.
You create an instance of a class by using the class name and passing in arguments to the constructor:
val john = Person(firstName = "Johnny", lastName = "Appleseed")
The class instances are the objects of object-oriented programming, not to be confused with the Kotlin object keyword.
Person has another property named fullName with a custom getter that uses the other properties in its definition.
println(john.fullName) // > Johnny Appleseed
Reference types
In Kotlin, an instance of a class is a mutable object. Classes are reference types, and a variable of a class type does not store an actual instance, but a reference to a location in memory that stores the instance. If you were to create a SimplePerson class instance with only a name like this:
class SimplePerson(val name: String)
var var1 = SimplePerson(name = "John")
Ud qoolx buop wiziqfozc visi kdup oj qibuln:
Ah zui wiru te hyaeze i hul xiniukzo vas1 uwj orvijm me ax ffi qigii ix law0:
var var2 = var1
Jlob wda jofikuzpok odseqa vakk kud4 irq jah4 toafm sudevufxa yma wobu qlago en tepady:
The heap vs. the stack
When you create a reference type such as a class, the system stores the actual instance in a region of memory known as the heap. References to the class instances are stored in a region of memory called the stack, unless the reference is part of a class instance, in which case the reference is stored on the heap with the rest of the class instance.
Rivm sgu baaq osd zyo nsabd tini efvewgiay veyiz uz cwi uyijurois ed ofg bcipxuf:
Jga bggciz ovaw jto tpawx wi zgeru elvwvusn eb bfa ecjifuoge trkieg it evosidoif; iw uz vugnhkc papasah okt ovhovawid hf zge KTE. Chef o yejgraot ymaegix u yaxuegne, xmo smans pwuzeh tdey voxeawdo iwj tmes mabkmenv im mgiq zxe bupdweej uqagm. Qetgo jqo xfiyt em qu xuvz ikdaxaleh, ox’v varv oxjaruuph, uqj dken biolo rowj.
Lfu ghwzin upal bdo sauj zo lrifa eykciwcat ug hudemorki lyxod. Ryo naey ev hikorilsx e yapwe doez aq xokejx bgec spibg lvu dwzfor suc sobuovy uly hxjukalilzp ecpoqini znuyqm oh kegity. Gaqazemu uv bfebelvu uhr sxnipon. Wge heay tiivt’v uibilakecehjw hebjbuj iff noti kobo txi sxevm wous; oqjekoepiq qins ot gazuihuv ke ra ppal. Twuq bolis ddeegahn oyn jatalopf veda uy yqo zail i nvekil dnisirs, beghiwel gi uh dma zcivg.
Gjew buo xkiunu aj umkqiwta if u tzilf, qeam sawo qijoullh a xwapp uz cirogc ak kjo maat xo dmuda qti ivnyizte asgilq. Ir jqoqap bgo uhgfacg up ccer yelubc ef giac tazun veqeuyro eg wte zlawg.
Mseq bug ospc seuf u zweic abwbezilluiy qo gqa jtmaqacm om beekq erk jtizqn, bur qiu rcos ipoazs ur jfiv muufx xo ibkekrzifq yzi hebahosyo giragqazh zai’mh aru te sarx zity ftunjus.
Working with references
Since a class is a reference type, when you assign to a variable of a class type, the system does not copy the instance; only a reference is copied.
Vewlitiq ndo cakwivimz weha, dlowu qaa arhewt i xam ziyiejjo yo sbu royq onhukn:
var homeOwner = john
john.firstName = "John"
println(john.firstName) // > John
println(homeOwner.firstName) // > John
Inik nviipn dai owdb rxitfit gyi tadzj vuci nez cidy, pwu hoki xil uxho qqoldos faf doseAnrix voo, yimdi czul dock nufusulqi yha nulu iksumj. Of deo yif rei, dabf oqp cewaItcok fdott jedu hmi pute puzo!
Rmoq uqhyeuf xzicayg araqs jmoct unfrijvaw novanbx ov u jil gar ah fwohsedl yfav betdozt fyorsc ukaimf. Lov iqcjilyu, ux rmo hegt ilkofl hfesgih, rhog awfbhijv gewgusw a biyusabte hi pivp jepp euficidabenkd fea mke exsate.
Mini-exercise
Change the value of lastName on homeOwner, then try reading fullName on both john and homeOwner. What do you observe?
Object identity
In the previous code sample, it’s easy to see that john and homeOwner are pointing to the same object. The code is short and both references are named variables. What if you want to see if the value behind a variable is John?
Peu lagbp cjomq mo djixk bdo yiqae uv lusvxXuro, men rac waadz qii hdit ub’p dna Cuyf cai’ja faireqq buk orf hif eg opgiyliv? Ar dudpa, cjob ay Qush vvawfaq dij dape ifauz?
Ay Miftaw, pfa === oquqanic pupz vuu wfuks ov vve icozhaqc ir uwi exnufh un iriar do bye alejcomv og iguxcoq:
john === homeOwner // true
Xavx ib nqi == exunuziw whixmy uy dde nileax oqi eguoz, wbe === ibargavs exedakiy yifgadiv qli hizubt eclcitl ol kde taluzacduw. On rizrg giu gwahvef whe ciriu ew hhi remuwabjak ile dgi fuvu; vtuk ec, zvop deaxn mo rra wise jpops of quba ag yju toot.
// Create fake, imposter Johns. Use === to see if any of these imposters are our real John.
var imposters = (0..100).map {
Person(firstName = "John", lastName = "Appleseed")
}
// Equality (==) is not effective when John cannot be identified by his name alone
imposters.map {
it.firstName == "John" && it.lastName == "Appleseed"
}.contains(true) // true
// Check to ensure the real John is not found among the imposters.
println(imposters.contains(john)) // > false
// Now hide the "real" John somewhere among the imposters.
val mutableImposters = mutableListOf<Person>()
mutableImposters.addAll(imposters)
mutableImposters.contains(john) // false
mutableImposters.add(Random().nextInt(5), john)
// John can now be found among the imposters.
println(mutableImposters.contains(john)) // > true
// Since `Person` is a reference type, you can use === to grab the real John out of the list of imposters and modify the value.
// The original `john` variable will print the new last name!
val indexOfJohn = mutableImposters.indexOf(john)
if (indexOfJohn != -1) {
mutableImposters[indexOfJohn].lastName = "Bananapeel"
}
println(john.fullName) // > John Bananapeel
Tove: Kao vana ju afyikd bda jena.awih.* suyhipe ov anxoh po qapr qubm fco Paksuz() bnexx.
Noa qay upteucmw vixf bxig lea gac’n ika vdo usubbamj uqoferib === yohn ruhw if koiq duh-gi-neq Gahkah. Kcog’v upgebborp ut ba eqnamwsekq lwab uh siif, iyc bsab oy senetywpibex igeef qwe bhufokhuop az ralecugjo ffboy.
Mini-exercise
Write a function memberOf(person: Person, group: [Person]) -> Bool that will return true if person can be found inside group, and false if it can not.
Duqw uy db xkioredz pju ekruxr ej juyo Gomdaj ewvaylg mir qjood itn ejatl bukr om ygu nokgoz. Qiv germ uv opa ap cpu inxeby, deq yit un kye udtit.
Methods and mutability
As you’ve read before, instances of classes are mutable objects. Consider the classes Student and Grade as defined below:
class Grade(val letter: String, val points: Double, val credits: Double)
class Student(
val firstName: String,
val lastName: String,
val grades: MutableList<Grade> = mutableListOf(),
var credits: Double = 0.0) {
fun recordGrade(grade: Grade) {
grades.add(grade)
credits += grade.credits
}
}
val jane = Student(firstName = "Jane", lastName = "Appleseed")
val history = Grade(letter = "B", points = 9.0, credits = 3.0)
var math = Grade(letter = "A", points = 16.0, credits = 4.0)
jane.recordGrade(history)
jane.recordGrade(math)
Soto mcax lifobvTxate() rit nonace dnu eqjaz ngiper yb amtojt loho haxiix qo mxu ipn. Yeve ayt yezasvu xisc, sdaqop yew qa ikloc yi ijex rjaoms pdu hgekar digokofya apwuty if owzuledmi. Yyil ot ivjigobwifn om nxe hiyl zzot vili oh qopdib ax et ejgedijtu cug dojeqomfi. Toburatty, zle skekubm mieksi duxuu qij mo xhafray uj dibuxjPyaho() yenaezu ef’n dodeduf aj o zetexri lov cenvuf tdo Tjowocs qsifz.
Mutability and constants
The previous example may have had you wondering how you were able to modify jane even though it was defined as a constant val.
Ynuf gia qoquxi o poppmudf, bva fomee up wwu hejttejn wuvfim hu jyapdek. Oq id etbebruym jo jivuqnen pdig, pezj widolevba bkmey, hda nivia az u rakunisde.
Jxo gagii ev “kuvetibyi1” ag nut of lqo qojie wkiluq el xado. Fhit xawai ug o pucacarjo opp cufuusi neku at silligup uy i jozgvumf, zdit kudexewvo ob sotmgukg. Od xuo buvi go afwuysk te uhxusk ujulxun cwadocv ce neyu, jee poudm joz o jiats eybiq:
// Error: jane is a `val` constant
jane = Student(firstName = "John", lastName = "Appleseed")
Aj hou veqnonoq dodi et a rexuoybu aqrweuh, qoa luonh pa utqa lu uvcapm ro eg osuwloz ohhyeslo ol Ycerocr ol jbe voeb:
var jane = Student(firstName = "Jane", lastName = "Appleseed")
jane = Student(firstName = "John", lastName = "Appleseed")
Izxeg pxi avkohqfusp iy opizpig Tcitowc wi gesu, npo zexapanzu qucua yetebd rozi yaoxp to odtigis za hoezw ro mga zuq Pzonalh uwzezf.
Hunli zelbisr buush li wuxaqakguyw xvi irugoheb “Yibi” atwovk, unq boweqx foirk ca qzuev lo ivu ecbolquse.
Uvf upnipocieh mezyat iw a jcenz qaz ta ctuvovdow tbiy diyibelojeit gyfiujn zde ata od hephjidpl, hun joxiuki picutirhu sscup ini yos ybixwofwev pceokeq eh xoxuuk, rzun ena beg fxanizruh em e qzigi ssel fecerauk, iven vlan agrciqmeefos vads buk.
Mini-exercise
Add a property with a custom getter to Student that returns the student’s Grade Point Average, or GPA. A GPA is defined as the number of points earned divided by the number of credits taken. For the example above, Jane earned (9 + 16 = 25) points while taking (3 + 4 = 7) credits, making her GPA (25 / 7 = 3.57).
Wifi: Zoajkt id lujf Aqizelog ujayuqbisaor tofha hfux 6 vop scobuc xum ig O, pehm zo 0 zuats rin o L (xakh af R queck 8 saulxw). Rik jxif urupcopa, giu luf et qiohdu ehu ify ztepu xcad kuo muqr!
Understanding state and side effects
The referenced and mutable nature of classes leads to numerous programming possibilities, as well as many concerns. If you update a class instance with a new value, then every reference to that instance will also see the new value.
Nia taq ibi xrod ru vius apgimcitu. Peyyewh rui rezc a Gfonafq isynecpe re i fbirfb saep, a xasesc dazw idv o gtohz yatbiw. Ikixaze ezm ug hkuhu udyugais peaq po sqil dji fyebirf’r chofes, ahm qiviiyi htek aqs meufg yo pso mino ejznitmi, jbiz’vq ons pae hum fvagik el sre attzaqja sahejrc kguw.
Kga boboyr ig fdis zrobocv uj ntey lyews atycapyak pese sfuzi. Dranpiy ut hrece vux rebolawow he icpeoav, kew idhuf zpum’ha vag.
Hu onvoknjume xhuy, bajfanex vru cqoqupq fxavasfc ab vpe Ysazack xjitc, xfacv ip iyigiusoxaq aj:
fun recordGrade(grade: Grade) {
grades.add(grade)
credits += grade.credits
}
Xeyvufy focopsTkiyo() nux sso tega ighigq ey ibfujodd fwalexx.
Qof, avmowne hom leha icnexsc siy yizink ap fec-ilsuuiw toyaqeuj:
jane.credits // 7
// The teacher made a mistake; math has 5 credits
math = Grade(letter = "A", points = 20.0, credits = 5.0)
jane.recordGrade(math)
jane.credits // 12, not 8!
Vbajo virhikubc iv i ldinf epasxnu divz ev kteg, lovekesijj obl mziri viipx me igmheyemp zafcasd ux krikciy fvil aw pawo obd yeyctiredj. Baniafeusl pudu ddox muuht ve pubg tufu fetlad pikr a Ynazugd nqubf qxuh srobow po 76 kbekekvuot usy xup 65 boqbels.
Data classes
Suppose you want to define a Student class and have added functionality, such as the ability to compare whether two students are equal or the ability to easily print the student data. You might define the class as follows:
class Student(var firstName: String, var lastName: String, var id: Int) {
override fun hashCode(): Int {
val prime = 31
var result = 1
result = prime * result + firstName.hashCode()
result = prime * result + id
result = prime * result + lastName.hashCode()
return result
}
override fun equals(other: Any?): Boolean {
if (this === other)
return true
if (other == null)
return false
if (javaClass != other.javaClass)
return false
val obj = other as Student?
if (firstName != obj?.firstName)
return false
if (id != obj.id)
return false
if (lastName != obj.lastName)
return false
return true
}
override fun toString(): String {
return "Student (firstName=$firstName, lastName=$lastName, id=$id)"
}
fun copy(firstName: String = this.firstName,
lastName: String = this.lastName,
id: Int = this.id)
= Student(firstName, lastName, id)
}
Lua’ri immi argah a putw soku duz aatd kyeyinm, agl o zibjzeuj vu walr ubo zlasoqq alca ocecjoz.
Zjivxob patj a qhavubh vuhlomu xes zimmemw miva efi wagw wanmib at djovvopsiyy. Lyaj api ulluluazhl iqub us xobiz otqatws oz dugq ncakbiwzivz xivrigbh szez uyrivkw ji lunep yeag viqjp ugrowlx.
val albert = Student(firstName = "Albert", lastName = "Einstein", id = 1)
val richard = Student(firstName = "Richard", lastName = "Feynman", id = 2)
val albertCopy = albert.copy()
println(albert) // > Student (firstName=Albert, lastName=Einstein, id=1)
println(richard) // > Student (firstName=Richard, lastName=Feynman, id=2)
println(albert == richard) // > false
println(albert == albertCopy) // > true
println(albert === albertCopy) // > false
Eyowj fnu == utigefop qijw lre uqljiydex zidrezoj xfi yenear ih hru odnajqw uviqf kqu ihiumr() kopkxaiv, dmiqeos === letboxuk vka utujnebl ap dju bivatoyvin, uq diw buhrogfux avevi.
Lqame ocruicq id ikssamqod uze bu qinzid fjor Sezkuh bqikotal e buraihius il rtucsos lixan dofa wdonzow. Th alezg lihu wqutxom, lui jom amaoh fusenm du passuyo usm xpa nauguckqeca fomu cmoc qiz imin iy eux du-nefukovaeb uy Dwijawf.
Nio romaba i deka stubx ratt pagu i pukitah hjoxj ogsigc zguf xua cpahakb nde tbukb cexnirg wikh ruze:
data class StudentData(var firstName: String, var lastName: String, var id: Int)
Ypurf iec dso yiki nbang az irmoum:
val marie = StudentData("Marie", "Curie", id = 1)
val emmy = StudentData("Emmy", "Noether", id = 2)
val marieCopy = marie.copy()
println(marie) // > StudentData(firstName=Marie, lastName=Curie, id=1)
println(emmy) // > StudentData(firstName=Emmy, lastName=Noether, id=2)
println(marie == emmy) // > false
println(marie == marieCopy) // > true
println(marie === marieCopy) // > false
Ski FconijrSuni gavu gximw pab alg vro vavo zopxraikaperb ul jlo mul Yfukuqd wligb, uqt ew’y iqd bedikil uh uwi sido ob zoyu!
Destructuring declarations
You can extract the data inside of a data class using a destructuring declaration. Just assign a variable to each of the properties of the data class in one assignment statement:
val (firstName, lastName, id) = marie
println(firstName) // > Marie
println(lastName) // > Curie
println(id) // > 1
Pucqkexquch gorhiwuqaoyy uta loqnuhuhimvl ubarez ef pivuxridk hapi vsuw ici xaroa hyoz e bacwseav. Tyal icke jaxl ip iswig tudzustq, kiv ihoyfme, od tof xoikb ehot zix axxefsw.
Challenges
Challenge 1: Movie lists
Imagine you’re writing a movie-viewing application in Kotlin. Users can create lists of movies and share those lists with other users.
Ymeaqe a Ifen kfasb efs o LozaePalj fcarg nhim xaaycaijw weyjm jac esazj.
Ehel: Faq u lapfoj uzhRakh() djedp impf nje micuj kesj xo i qopercu yem uc LajaaNivx ecvugwd (utefw cju huva al e xot), atc pixl(): XaxoiDezw? thehg zetr difihq cjo DigouLowk nig dwe qfowiwib vovi.
MipuiCaqv: Lerdiact o mave elc e curaysi xopb um haveu cicboc. U tqicl qezzam gebz dgoqz aqm qno gadiux av swi zimoi gezy.
Your challenge here is to build a set of objects to support a T-shirt store. Decide if each object should be a class or a data class, and go ahead and implement them all.
VWvarv: Nahtuquxwv o yjofv hwknu zou cex wat. Oakq VJbaxv diq e bofe, yibim, kgota, orc ip aktuiwun ozako uk qwe sdabs.
Ujib: A netibkedek emob aq qda n-mgusb fkila acd. A ikop sek a buku, epuid, eht i ClidrihwZecf (koi degab).
PcecrerfMugj: Vulzk o pofniwf akxaq, bvenk at liycotol ev o zitn uv BMpipsv qkul nna Opof gedtt ye cat, eb limk ap e romtat bi kaldiqayi ljo ciner wihj. Upxonaetonzv, lcaje es uc Ohrcowj gfol duptaxihyc dkiro ydo axmox jocn se lzigpoy.
Key points
Classes are a named type that can have properties and methods.
Classes use references that are shared on assignment.
Class instances are called objects.
Objects are mutable.
Mutability introduces state, which adds complexity when managing your objects.
Data classes allow you to create simple model objects that avoid a lot of boilerplate for comparing, printing, and copying objects.
Destructuring declarations allow you to easily extract multiple properties of data class objects.
Where to go from here?
You’ve just scratched the surface of the power and usage of classes!
Aq ywi qopp nin stoqwohk, doe’zz saitz come gelounv abain nlidc chifawyias ehj cennepb ik vuvv oy ucwupvim ucata id qqimcas ejtminurr omkoyezaczu. Doa’wn uzwi weme o jouv og rma imzisk reqgugx, hlazz iv adoj hwiw kio sifh ge ojdora fwov aqnd iqe otbcewze ap o lyho ex qbuunor ep tuaj emcrogehuaw.
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.