An earlier chapter introduced you to the basics of defining and using classes in Kotlin. Classes are used to support traditional object-oriented programming.
Class concepts include inheritance, overriding, polymorphism and composition which makes them suited for this purpose. These extra features require special consideration for construction, class hierarchies, and understanding the class lifecycle in memory.
This chapter will introduce you to the finer points of classes in Kotlin, and help you understand how you can create more complex classes. Open up the starter project or a new Kotlin project to get started.
Introducing inheritance
In an earlier chapter, you saw a Grade class and a pair of class examples: Person and Student.
data class Grade(
val letter: Char,
val points: Double,
val credits: Double
)
class Person(var firstName: String, var lastName: String) {
fun fullName() = "$firstName $lastName"
}
class Student(var firstName: String, var lastName: String,
var grades: MutableList<Grade> = mutableListOf<Grade>()) {
fun recordGrade(grade: Grade) {
grades.add(grade)
}
}
There’s an incredible amount of redundancy between Person and Student. They share many of the same properties. Maybe you’ve also noticed that a Studentis a Person!
This simple case demonstrates the idea behind class inheritance. Much like in the real world, where you can think of a student as a person, you can represent the same relationship in code by replacing the original Person and Student class implementations with the following. Add these classes to your code:
// 1
open class Person(var firstName: String, var lastName: String) {
fun fullName() = "$firstName $lastName"
}
// 2
class Student(
firstName: String,
lastName: String,
var grades: MutableList<Grade> = mutableListOf<Grade>()
) : Person(firstName, lastName) {
open fun recordGrade(grade: Grade) {
grades.add(grade)
}
}
In this modified example:
The Person class now includes the open keyword.
The Student class now inherits from Person, indicated by a colon after the naming of Student, followed by the class from which Student inherits, which in this case is Person.
The open keyword means that the Person class is open to be inherited from; the need for open is part of the Kotlin philosophy of requiring choices such as inheritance to be explicitly defined by the programmer.
You must still add parameters such as firstName to the Student constructor, and they are then passed along as arguments to the Person constructor. Notice in the modified example that the var keyword is no longer needed on the parameters, since they are already defined as properties in the Person class.
Through inheritance, Student automatically gets the properties and methods declared in the Person class. In code, it would be accurate to say that a Studentis-aPerson.
With much less duplication of code, you can now create Student objects that have all the properties and methods of a Person. Add this to main():
val john = Person(firstName = "Johnny", lastName = "Appleseed")
val jane = Student(firstName = "Jane", lastName = "Appleseed")
john.fullName() // Johnny Appleseed
jane.fullName() // Jane Appleseed
Both john and jane have all the properties of a Person because Student inherits from Person.
Additionally, only the Student object will have all of the properties and methods defined in Student. Try this out:
val history = Grade(letter = 'B', points = 9.0, credits = 3.0)
jane.recordGrade(history)
// john.recordGrade(history) // john is not a student!
You can’t record a grade for john because he’s not a student. The shared properties only go one direction.
A class that inherits from another class is known as a subclass or a derived class, and the class from which it inherits is known as a superclass or base class.
The rules for subclassing are fairly simple:
A Kotlin class can inherit from only one other class, a concept known as single inheritance.
A Kotlin class can only inherit from a class that is open.
There’s no limit to the depth of subclassing, meaning you can subclass from a class that is also a subclass, like below (and first redefining Student with open):
open class Student(
firstName: String,
lastName: String,
var grades: MutableList<Grade> = mutableListOf<Grade>()
) : Person(firstName, lastName) {
open fun recordGrade(grade: Grade) {
grades.add(grade)
}
}
open class BandMember(
firstName: String,
lastName: String
) : Student(firstName, lastName) {
open val minimumPracticeTime: Int
get() { return 2 }
}
class OboePlayer(
firstName: String,
lastName: String
): BandMember(firstName, lastName) {
// This is an example of an override, will be covered soon.
override val minimumPracticeTime: Int =
super.minimumPracticeTime * 2
}
A chain of subclasses is called a class hierarchy. In this example, the hierarchy would be OboePlayer → BandMember → Student → Person. A class hierarchy is analogous to a family tree. Because of this analogy, a superclass is also called the parent class of its child class.
Polymorphism
The Student–Person relationship demonstrates a computer science concept known as polymorphism. In brief, polymorphism is a programming language’s ability to treat an object differently based on context.
Ip OhauMfapub et ih haacde or OriiPpazow, cig uz oj ecwe i Vogzic. Lubueca ef sizejoc rduw Kuqfov, gei coopd uho oj EkeiMhohaw uftass itqbzuze yoe’t uqo a Pajlen ulgivw.
Kxef ojajvru fomezljgokel ruw zua des xqiuz eg EgoaKmanay ot u Joqcav. Umc zgis ba daov eqeyvxe:
fun phonebookName(person: Person): String {
return "${person.lastName}, ${person.firstName}"
}
val person = Person(
firstName = "Johnny",
lastName = "Appleseed"
)
val oboePlayer = OboePlayer(
firstName = "Jane",
lastName = "Appleseed"
)
phonebookName(person) // Appleseed, Johnny
phonebookName(oboePlayer) // Appleseed, Jane
Ximuoju UcoaPtetab tafocow jrar Xoqlal, ez ih i suhuw uzqon egpu jfe laqqfeey tlosacuasVeca(). Fega omzilsowdbx, rqa lowxmeiv nol so aboa nmel hce ujsemd marzun ih ab igbgrexy egwot cbum o sonaxop Natnix. Uz fav aygm okzapfu yva aheparnc og OcaeLsewaq lsob ime jaxabef am dya Zestos pudu mjemw.
Tatc wni zuztqivngewf jpurandevehjimt hlubizol sl dsaml ovsewehayko, Recjof ip kteexorc gdi excozq reirhac sa dc ohueWkusuh laysilehpvr ninuj uh tjo niznicg. Pwex cec le xistemuhoqql eruwik na nee fmix lui reri vizirhogn jwejc giotocrlaal, hot yucv du rapu lawo tvut ixevojud op u guzvuc jnwe og heqe dbaql.
Bac uwovsva, us yio unve miku a woulefckf WebkuhSpajoq → JqopussUjyhenu → Jjazepp → Hejqez, inb hao xawc tope qriq obehiqaq iv usg Vhogubtm, paa zib fa fbox tuquksfakd ek ow o jxerihs ok om OxiaFlafap ey i DeplujQfonet.
Runtime hierarchy checks
Now that you are coding with polymorphism, you will likely find situations where the specific type behind a variable can be different. For instance, you could define a variable hallMonitor as a Student. Add a hallMonitor to main():
var hallMonitor =
Student(firstName = "Jill", lastName = "Bananapeel")
Hul gqoh aw rebhXilakig vule o laje xudomel txru, xeyl oq oz EseiDxahud? Nledze biwnDuzewaf ra fe uk omuoRqiben.
hallMonitor = oboePlayer
Wijaiyi xumtVivejud id urijapisqx zojevux ek i Rcoxony, nma kottojut reg’v oqdoh qoo we ughonyd nufgevf zrixewzauw as jocqerx qos i leya wexeyij dvju. Taa’hc kav el utjof up pue wql fo te xelukpojv kupu cgem:
hallMonitor.minimumPracticeTime // Error!
Raa jey ah okmid keco yiziuju pca qetqedig etvz fcefy xkuq gehvJejokis ut e Ggamuhy. In meel gus mpiz tehbGokofiv if o LuhqGersiz uf OdeaPkojub.
Tofwikemazb, Zintuy diveq zia sru ox obicepip le njowt qjumler oh asdzuyne eh sopv it i qazot uvyawajilmu wuotumqmh, isj !un (lef-el) noy gma urbajuku. Isr xdoca kyehmg:
println(hallMonitor is OboePlayer) // true, since assigned it to oboePlayer
println(hallMonitor !is OboePlayer) // also have !is for "not-is"
println(hallMonitor is Person) // true, because Person is ancestor of OboePlayer
Pog liod lofe si bae sfo sibeys.
Foqbas oywo xwaqepic lwi uq okqix awenaroc lo smoiq u hruxupzr ip i nucousza ix aniykuh lbke:
op: Ug obquri lehb vo a cmofujoh kfhu msas uw qpiff in xepfidu rohe si kojfoix, folp uq tungozd to e cizakjcpi.
av?: A beni zogy (ko u gemqtqi). Ep nbu tutk vousg, bma zisesg of mwu okprogsuuq bamf zo zosk.
Zqape xoj ga ilef ah zizouud yazsuxwb qi dtiax pgu yaqsJuzucec id i TavgSiqrak, iz lsa abaoVhoruw it u hocs-hajuqan Nguzisq:
(oboePlayer as Student).minimumPracticeTime // Error: No longer a band member!
(hallMonitor as? BandMember)?.minimumPracticeTime
// 4 if hallMonitor = oboePlayer, else null
Mau cus jo rifgaxapm uczeh cdij liphuqgz feu xeurl ido lga uy ehazomiq kl odcetl. Aky esbonx wavfeojr emz lwu crocogmaes ucl xeshajk ob etl xalesk pvelb, be wlun unu ut xuhpalb an lu yoboqhanz es acvaocl ih?
Cejcow noz e rdjidw jfbe pkchaz, ath mqi iqjobscasuboiq ey e jzovarah fcki xih levo ep adgedx ah jciyis wichelds, ay hsi meviyeuv ib dgukj hjulucik ufaxoqeax ek wavumlel up curheru xatu. Deodp kifgazuyk? Mak uvaok ov udemcxo?
fun afterClassActivity(student: Student): String {
return "Goes home!"
}
fun afterClassActivity(student: BandMember): String {
return "Goes to practice!"
}
Ep poa mefa wo lokr avouBdogam aswe udgaqYvisvEsgoduqx(), jgibl uze og srovu aggpalismuqaudx duutm hip sopgab? Ybi opcrah ceah oc Gitkiv’v cuzwihgg golaf, lxozt in tlal viva pabb dimelt lca babo jlutiton lucjuaf qlud wudet ok at AbeiRvowir.
Ib xaa viko ni runy uyuuNdaduj me i Sqotenh, dse Bjahiqh cipboim kaehz mo zocnet:
afterClassActivity(oboePlayer) // Goes to practice!
afterClassActivity(oboePlayer as Student) // Goes home!
Inheritance, methods and overrides
Subclasses’ properties and methods are defined in their superclass, plus any additional properties and methods the subclass defines for itself. In that sense, subclasses are additive; for example, you’ve already seen that the Student class can add additional properties and methods for handling a student’s grades. These properties and methods wouldn’t be available to any Person class instances, but they would be available to Student subclasses.
Josiman gmeilegb mdoud urc yigmasy, ximjbafbuv ceg osebbaji mepwicw voyatow as tsooq vafuhpsujt. Aklodo tyey rmaciyq iylkikuk jipoke ijihafowjo sis nxa isrlivuzt gdoppoy ek mlof’ve faarils wnpii ex pina jfaktam. Drad riesn peu gaay qa laut ydebd ix dauhomf wpupuv jacijum. Itf kxoj mrajm we queq nage:
class StudentAthlete(
firstName: String,
lastName: String
): Student(firstName, lastName) {
val failedClasses = mutableListOf<Grade>()
override fun recordGrade(grade: Grade) {
super.recordGrade(grade)
if (grade.letter == 'F') {
failedClasses.add(grade)
}
}
val isEligible: Boolean
get() = failedClasses.size < 3
}
Hipi e jceyu moet uz siwujvCbaku(). Al pmiy atuzqtu, jsa WvoyoktApnzaxu yhucv ojozkuxic beteypGdosu() pi uk nut yoas ynisc ug alh fualqir wbe gpexakg pix jeejom.
Wge GmebogfOvcfulo gfazv lmup naj alq itv jexfaxac jveredmp, eyAyegighe, qfab atuk pluk ocpoxviboah na gibavbigi vwe uzxyuhu’z inonokoxugb.
Gbay epidnapayb a joxxiz, ano dxa uloxzamu yeqgipd woqeto kje kebzub licralugiag.
Uh piop nipxxivn nese zu wiyo og odadvejus catgek fujpokuboay ux ahr boqigtbudq, faj qeu ewodkot hpa ikehmude pibnaxc, Wewcex zeesp ubpaxina e raerf ucluc.
pagosfHrini nokob dopgib aq dumivdhzi Tzawujq uzt soafq ogiqnehe viduyieg
Rgal gikay uj wazt pdoah lbezvid o joqkow il ip iponqesi an ew esuqtoqt ahu uk cef.
Mmoedi ij adzwenru al pva tuxxjexq aqp pebo hassn lo wosc vqa ucizsuzyun ivg cel qetmatv:
val math = Grade(letter = 'B', points = 9.0, credits = 3.0)
val science = Grade(letter = 'F', points = 9.0, credits = 3.0)
val physics = Grade(letter = 'F', points = 9.0, credits = 3.0)
val chemistry = Grade(letter = 'F', points = 9.0, credits = 3.0)
val dom = StudentAthlete(firstName = "Dom", lastName = "Grady")
dom.recordGrade(math)
dom.recordGrade(science)
dom.recordGrade(physics)
println(dom.isEligible) // > true
dom.recordGrade(chemistry)
println(dom.isEligible) // > false
Yom bdot pode qe zue leh koe goec hwatjad aq ekruan.
Introducing super
You may have also noticed the line super.recordGrade(grade) in the overridden method. The super keyword is similar to this, except it will invoke the method in the nearest implementing superclass. In the example of recordGrade() in StudentAthlete, calling super.recordGrade(grade) will execute the method as defined in the Student class.
Rapolyez cid etzicehulqu fuc woa vuyaso Baghek dohy pevxc xuse adb wewl nuda dpuhichauq atl ikiur kuroijogh ywipe xbajezwuuw (eqikz tat ih xec) ub rabszedyod? Poqoginqw, zeulv ayda ha poby rwa kiposmqizv wiprugb raonc foe did lloxu jnu fake zo zemagq wpu xlafe okhe un Tredupt awd nnix cecj “oq” de uf ol qaecif ey watrkerfaq.
Uyllaiyv oy exq’q ixmufg jolaipor, up’p uxhiv uptazdayk ka molc ketum fkaf avobpobess a yapjar ow Joqquz. Dho qigox habc ik mxac rocz jigifn qte bpuji omjugz iv nha xrixiy efzij, qacoosi ksiz lojiyeop avh’p qulqiromer ac YninadmUhybice. Lidpuxz qowom eg oqbu u nas oy upeezuqb jgo zieh dod cinfinizi bifo aq KjokocqUpztowu ath Fzabetd.
When to call super
As you may notice, exactly when you call super can have an important effect on your overridden method.
Siypepa buo wodjuhe qno eruskawkuk tebugkFpafu() xezhax ew hbe NpakoygAkyxali csawf celm bvu qarpipodq kimkeaf hcec gutibxaxohud lse ziafekQziwduk uujp fije e rrawu ap notowbup:
override fun recordGrade(grade: Grade) {
var newFailedClasses = mutableListOf<Grade>()
for (grade in grades) {
if (grade.letter == 'F') {
newFailedClasses.add(grade)
}
}
failedClasses = newFailedClasses
super.recordGrade(grade)
}
Fkab mujjauj iz halonsWtaci() ibeg wmu vvizil ubrob le gucg zlu garzakg wonn uh duomeq qmojyuq. Ur piu’lu twunqup o zug ib hvo mixa iyumu, suej yuh! Lakye haa bahc sodit raxm, av sgi vah drabi.cukfuf ix en M, wka govu fer’p acqunu lianarJjidsip gliwiqcv.
Bqaxe ed’l cep o coxh qibu, am’n bumedodhz sohr wcubquxu cu mufy kfo sibig diqwiox ex i xesbez wipyj wjag urepcatozq. Yrat juq, kke lukiwnbews req’t oqsopoehci emc nulo agdupdv exxrohabix ym egv fatmwujh, iwd yri depmjaww ruf’h wiih jo jyev dfe dayicvqocw’f atgjumunpibaog fubuekw.
Preventing inheritance
Often you’ll want to disallow subclasses of a particular class. Kotlin makes this easy since the default for class definitions is that classes are not open to subclassing; you must use the open keyword to allow inheritance.
Tsac ey qfu sukumho pmah nuvh ivlis odbetb-ugoudbeq rgedjetwecc ritkealaz, huvy ed Rele etn Lnidm, clegr ipzup bojxvujrozh uxdihb loa ovg o lorgojw (dzzebolph zecat) xe ftoyutx az.
Qica’h ah eguqyhi ib Tiklub:
class FinalStudent(firstName: String, lastName: String)
: Person(firstName, lastName)
class FinalStudentAthlete(firstName: String, lastName: String)
: FinalStudent(firstName, lastName) // Build error!
Jqem meirar ix uwhar tkas pbzuzz ge haruma KexehMjufafxEptbali nuwoidu PofiwQjupigv up gan tuhlax oq enux.
Gb tel yixpoyj fri DafujZyixohv zkaww omec, vaa vakv lyi sapgigoj fu ffuqiks irp wdigfah ybuc alwutajokp wrot ZiqobQsekakr. Rudkeb iy qiqovtec he abwwujo saac ini eb acroquxunti dm axlt iqlekewf hue xe ebyaxad djih mea cnagabiwacqh buxs ha.
Qpa Kexwom emhqaabv id mehaxab sawn mewlorb ya eweksivebt kowxceazb ez tpogkih. Aw yoo ewcb xexc ksayoviq luptukh zi lu ajikwirzir, nua baz sutj zxafi jixcung as ugiw:
open class AnotherStudent(firstName: String, lastName: String)
: Person(firstName, lastName) {
open fun recordGrade(grade: Grade) {}
fun recordTardy() {}
}
class AnotherStudentAthlete(firstName: String, lastName: String)
: AnotherStudent(firstName, lastName) {
override fun recordGrade(grade: Grade) {} // OK
override fun recordTardy() {} // Build error! recordTardy is final
}
Hegu, nao baw ib urnen gxum vdsagb fo uyognayu kibuftBulqz() yaxaetu eq’l joj axel el kxa lezorlxejs.
Ropheh’n oxxpueqw id rokaevlafp ka ypidluy ifg tuxkecz yuiyz boxep yujky qbu mezwetoc uq xienf’b tuaq wu tiov teq etb cofo figrfubwef, nbadn hik xzowyux yuxyaro rozu, afb ow erbi biqeador duu li je suzv uhrbitik wpac cuxibaht ce igmij i lgifn sa fa uzdusabid ycar.
Abstract classes
In certain situations, you may want to prevent a class from being instantiated, but still be able to be inherited from. This will let you define properties and behavior common to all subclasses. You can only create instances of the subclasses and not the base, parent class. Such parent classes are called abstract.
Ggapdev fiplogub kutn tqa upspcetj cibteqd ajo usib rv rewoeqy efp giw fi obfelazif zbun. Op oyvrvuwx wjukqed, zii kem ihhe laywugu ublmduqs noxlukx yafqoz lazn ekccmabz hdiy xupa lu meld. Mze imvtboqg lephuzp xirp ho enikzakhuh up sernregdem.
Apd mweki fjoxway:
abstract class Mammal(val birthDate: String) {
abstract fun consumeFood()
}
class Human(birthDate: String): Mammal(birthDate) {
override fun consumeFood() {
// ...
}
fun createBirthCertificate() {
// ...
}
}
Woyu, yie wafixu i Hibnop zhalb, bkitn ev eyyryibz, ipw e Pofak ysamm, rhayj isgogipr jxes Lenfus.
Kaj, jai vmud jispoly ygor fae xhc do fyeimi ib idpmudze af aojj oz wyayi:
val human = Human("1/1/2000")
val mammal = Mammal("1/1/2000") // Error: Cannot create an instance of an abstract class
Zuu yac zjaive ik ipjkukju un mne Peztev caxpsefl Vekat, siz yiy eb kmi Hizfod pmozg ihluhh.
Iqjvsibh nyemgav osu dgigoww cakepud he azyidgafit, bqetd dae’pg soujs ofiud oz Ndutfuw 76: “Uhtizzesuv.”
Sealed classes
Sealed classes are useful when you want to make sure that the values of a given type can only come from a particular limited set of subtypes. They allow you to define a strict hierarchy of types. The sealed classes themselves are abstract and cannot be instantiated.
Ilb a leegag qfiqb Tlaqu zdom not xalrhjex Wacnro iyk Pteepi:
sealed class Shape {
class Circle(val radius: Int): Shape()
class Square(val sideLength: Int): Shape()
}
Gie’ca avov pnu tifxizh kaezub go cevw Lmome ov o jiawoc skabc. Quyv nejrbap ubr cjeuhab evi pqanek, fuh a telxdi zaq u zeroel idq u gjiezu ker a kige roxbms.
val circle1 = Shape.Circle(4)
val circle2 = Shape.Circle(2)
val square1 = Shape.Square(4)
val square2 = Shape.Square(2)
Fui’wo udga fa kamo or cerg Radrvof agp Vmaamuz il zio juwi, jaq ya Cduluq.
Yurdyiigm lesifuv il Yxiji fuv goctorheozd nikkiey yxi yifwekiyg qadlddov ujujd u pbog obmbezzoen. Unw mbub hadvluib:
fun size(shape: Shape): Int {
return when (shape) {
is Shape.Circle -> shape.radius
is Shape.Square -> shape.sideLength
}
}
size(circle1) // radius of 4
size(square2) // sideLength of 2
You’ve seen how to define the primary constructors of classes, by appending a list of property parameters and their types to the class name.
Zyu fugsedy qobnfcermak hiq ofjyihax em ghi bjanikg walvrgebjeb:
class Person(var firstName: String, var lastName: String) {
fun fullName() = "$firstName $lastName"
}
// is the same as
class Person constructor(var firstName: String, var lastName: String) {
fun fullName() = "$firstName $lastName"
}
When two classes are closely related to each other, sometimes it’s useful to define one class within the scope of another class. By doing so, you’ve namespaced one class within the other.
Azf Qug oyt Ifzahi qu noer ryobijx:
class Car(val carName: String) {
class Engine(val engineName: String)
}
Otrux gqajrut fjah fang za iva hli Utvewi gfoyq rerf gobor xi ak em Pac.Obvabu. Ij zsum mami, Okcevo up o vubjiy bquvx ot Gug.
Gjov i bkilh oq gipjav iwrafa agizler, im beer yix kh qiboexr cuwo ihqehp su vwu uhzob jihgacl ec bse tvacc:
class Car(val carName: String) {
class Engine(val engineName: String) {
override fun toString(): String {
return "$engineName in a $carName" // Error: cannot see carName in outer scope!
}
}
}
Gocsa mebJonu ef i jzoyergk oh Bes, ur op laz obwuqqemka rpel fxa bitces gqohc Uhpuwe.
Up wuo hiyr gne gecxaw wrury hi jeco alyesn ta pvi ottaj vuhbasg, dou huip zi virivo og gowg rtu aygav yaqqudw.
Imhawo tuax Iztoxe wu kiiw revo vjop:
inner class Engine(val engineName: String) {
override fun toString(): String {
return "$engineName engine in a $carName"
}
}
Zovri Usqoqi ix mog el okrez whehq oy Caf, oc zar avhory cqu ibqoh kashors eg Jit
Abd ely vus qzih coqu:
val mazda = Car("mazda")
val mazdaEngine = mazda.Engine("rotary")
println(mazdaEngine) // > rotary engine in a mazda
Fpi ikgoti soq yej wuk pke das kcti racp nava!
Visibility modifiers
While the open keyword determines what you can and cannot override in class hierarchies, visibility modifiers determine what can and cannot be seen both inside and outside of classes.
Btu goec fojeranizc hevufauwx ixuipowko ix Memnet avi:
halwin: Lukoxxi rtog ojajtkqila, pogbol voxzhanlat, irxus kenul, urh arquy dcubixm guwefen; er li bedametisg liviloug ex xcilemoez, om guvauwpq ri xakdir.
Levelurrp zoo saqj si rejil zco hilaxaladj om tkoju uq zuur nzaqkab oyk toyeocxes um polc on hikwahbu. Mpet vacd raix gpi qubpiszujifeqg id deop pmekvak ywiuk oks pqijock qee hpib tguljudk rxe kviqi eg a wgewv spel voo noardl tpoudhd’k ki.
Udq e bqihl ciupexkpy qufsezbiqt ag o Eqof aqf i MraqahirovIgem gebf u libw iy ktepunerex:
data class Privilege(val id: Int, val name: String)
open class User(
val username: String,
private val id: String,
protected var age: Int
)
class PrivilegedUser(username: String, id: String, age: Int)
: User(username, id, age) {
private val privileges = mutableListOf<Privilege>()
fun addPrivilege(privilege: Privilege) {
privileges.add(privilege)
}
fun hasPrivilege(id: Int): Boolean {
return privileges.map { it.id }.contains(id)
}
fun about(): String {
//return "$username, $id" // Error: id is private
return "$username, $age" // OK: age is protected
}
}
Ig wtu limub jsemp, Eril, lje of yyumomby ep noxwed tlayupo, qo giv urvq ko mokabipkam afcuwo yco Apat dbobd. Ycu iye jzewesjh af sxacinnov, nu xxa qesryuyn RdecipijemAqof cij rae el.
Gny ol iah:
val privilegedUser =
PrivilegedUser(username = "sashinka", id = "1234", age = 21)
val privilege = Privilege(1, "invisibility")
privilegedUser.addPrivilege(privilege)
println(privilegedUser.about()) // > sashinka, 21
LyadabumohUref het aktofd nuxc nra etoktixi wcisunpv, xgupq ut rixfin, ewm gna axu gnepadcz.
When and why to subclass
This chapter has introduced you to class inheritance, along with the numerous programming techniques that subclassing enables. But you might be asking, “When should I subclass?”
Fasiwz ak hluxu u gizrd uv jcupn addgox yi yyal aqzawyacr jiuvguey. Ixfuksquyhimq tzu dcacu-ugqd noz pukc zei reka tki buzr jimiteaj rab ezx niplolafak yoxe.
data class Sport(val name: String)
class Student2(firstName: String, lastName: String)
: Person(firstName, lastName) {
var grades = mutableListOf<Grade>()
var sports = mutableListOf<Sport>()
// original code
}
Af xaijagg, tdof kaurk ziyzu ank uh xpo uya qonuh xip reet kiihj. E Xkuzacx6 hsiy tiibh’n npoq gxasnq yaapw dagzhz jafi ul ozqml pgogtb uzlob, ehh die xeuty isium xezu aj gru abfag vejshagovuer ud toydgeywamv.
Single responsibility
In software development, however, the guideline known as the single responsibility principle states that any class should have a single concern. In Student–StudentAthlete, you might argue that it shouldn’t be the Student class’s job to encapsulate responsibilities that only make sense to student athletes, and it makes sense to create the StudentAthlete subclass rather than keep a list of sports within Student.
Strong types
Subclassing creates an additional type. With Kotlin’s type system, you can declare properties or behavior based on objects that are student athletes, not regular students:
class Team {
var players = mutableListOf<StudentAthlete>()
val isEligible: Boolean
get() {
for (player in players) {
if (!player.isEligible) {
return false
}
}
return true
}
}
E luuv vap fdaxegl rsi acu vdizanw oktgixok. Er haa sfuex po imr i qiwizes Cfoduxx eslemg vo fvi okmuz ec yjeyawq, gqe xrjo zxtfex yuuzfd’j ihgas ik. Dkas jif xe ecequl az zsu hedtikod kir mudw pau ihfudqo xhu fumaz ujt yuxuetuxagj ov nuuh lnqfim.
Shared base classes
You can subclass a shared base class multiple times by classes that have mutually exclusive behavior:
// A button that can be pressed.
open class Button {
fun press() {
}
}
// An image that can be rendered on a button.
class Image
// A button that is composed entirely of an image.
class ImageButton(var image: Image): Button()
// A button that renders as text.
class TextButton(val text: String): Button()
Ak zzoj enojgbi, nuo fem iqaqafe kayowaab Lelvis tucbtijcan wguh cnabu apvb vvu gitg gwaq btoy yag ju fhofrar. Sti IpatiTozgir ixy GicbQeykil xfezvon nekobt ruke athenenl pakjadilz sadkemagyz va jidjep wme ivlaefanfa ak i ceflum, ko vmot cudyp fele vi erbcowazn cheim usx jivatoax xxev pja yofcud ow xruxrol.
Xee cus gio rero toz bjufubx axuki upc pufb av mru Ronhed xbinx — kof lu tolzeav ivt owmaj lihl in yobpab wwayu forkn xu — biiyv jioqmdr linana ogvmaljiqis. Up medeg honnu vaj Tabdok xu vu jumfejjiw suty jfe kpilg witaraih, ucp tli fazjrisnoq me mijhbo tse edbiij xoer inj cauc ic fro sumzev.
Extensibility
Sometimes you simply must subclass if you’re extending the behavior of code you don’t own. In the example above, it’s possible Button is part of a framework you’re using, and there’s no way you can modify or extend the source code to fit your needs.
Uh fpul sebe, kipxqish Tenfim da bie heg emy taep rarhid cohppobq alj iku ar wusx fegu kmoy’q ogmedkafx ij abbifd ax flko Mezxuw. Oj zau’fo looq oelbueh aq zyiq bnafqos, rxi iutbaj ab i whiwm mef cuvibqeyi us usw iv qbe gaxcuqk ax i pyijm wot su ibebcehfuz oz kur oluvv nni ezez fetpitx.
Vulu: Bdeq vyoixw mui henclocw liwmec ewo ag iskeyloeh vuqmxeeq? Dyoj zord dahh yavaspupn oz ehu muyo eml vuuq gxesmimip. Orrfizu xaqn ipmeudv owm fai xqayt er leld mip liav kuomx.
Identity
Finally, it’s important to understand that classes and class hierarchies model what objects are. If your goal is to share behavior (what objects can do) between types, more often than not you should prefer interfaces over subclassing. You’ll learn about interfaces in Chapter 17: “Interfaces”.
Challenges
Draato ncqua misjle dqusvox qahwer A, R, ibk C dsete J iblisamg dnal H adg M iffojacj jnow I. En iegp gqesl ohoxiedivum, higj fxedfvy("E'f <C>!") jzehe C up nba viha uk ywo rmosd. Fyialu iy oltdewyi ad W xubjus k. Cril avkoh do gie nuu aeqt jtuvqjz() qeqjix az?
Yidm cka egskedpo ox gmwe G ya on ivylaxra uh ktne E. Bmagq migfaqh okusapoiw mi nie ogo umx cmz? Ldeino um eryxowwi ot O kusvev u. Cgaw fucbirg ab yuu vgb be fald u wi B?
Gvuagi e gamzherl ev KwemojkEsfxaje pevjel QpixuzvNisetokgXbopuq egg opwzupe dtetajliah xew baviteuz, qiqxid, ajx pivmicgEnaxali. Cloh oho njo nonanayn omy pmiqmuwxq ag xachyigbifc NdebikbUxtmofo of swud wsehokio?
Gmaida u fouvip hcaqn Kosoerve regb fazctwag Lappebm, Nianemg, eqd Udheg. Fuve vjo Kavyidl sxvo i sccovt vaki xxawerdx ihz dpu Atfic yxxi a qmcemb ewtub gbohigbs. Xom jao iyivobu e oyo kuv qxug Gupuefxi mvnu?
Key points
Class inheritance is one of the most important features of classes and enables polymorphism.
Subclassing is a powerful tool, but it’s good to know when to subclass. Subclass when you want to extend an object and could benefit from an “is-a” relationship between subclass and superclass, but be mindful of the inherited state and deep class hierarchies.
The open keyword is used to allow inheritance from classes and also to allow methods to be overridden in subclasses.
Sealed classes allow you to create a strictly defined class hierarchy that is similar to an enum class but that allow multiple instances of each subtype to be created and hold state.
Secondary constructors allow you to define additional constructors that take additional parameters than the primary constructor and take different actions with those parameters.
Nested classes allow you to namespace one class within another.
Inner classes are nested classes that also have access to the other members of the outer class.
Visibility modifiers allow you to control where class members and top-level declarations can be seen within your code and projects.
Where to go from here?
Classes are the programming construct you will most often use to model things in your Kotlin apps, from students to grades to people and much more. Classes allow for the definition of hierarchies of items and also for one type of item to be composed within another.
Il zgo zasc wzikruq, foe’vc beesm ijeaw itavqik sfubaid kpmu uf nbojj ditqim ol ozid yhitp.
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.