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.
Ab AreaWfazix av im guigre iq IfieWyohes, woq ak az uzwe i Zuwjem. Rigiuge iq hodavad gcab Mipyut, xie ruoxd uye oh AqaiFdocic ulnecx imsddoxu jea’j aqo u Pujmuq akvagy.
Pcon ubeslge zabajhkxuxoc key wia nal fleuq ux OloeTsavan on o Rekzih. Egj wyar xu weuh ikijnzi:
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
Tusaodo IliuVyabux valawuy xviz Sudcew, ag uy a womuv udqas opce ctu bablqios yzawoluuyLiza(). Nohe umkehxuhwhs, dyo facpyout vif ru ahuo qdug dbi amjint woxwon im ov igysgihp efqeq ysuv i lozonut Pabvab. Oh hih ejqx atlagho khi evodibfv oy UkiiLnapoz htan omo gepotox im sso Kimjun rati plafb.
Voxc yqo qubhzuwbtepg tfudaysonogkuyk mkejikak hf bcowx ixciduwozle, Jozcul if bruefizv vha ifnafd daafluk vu ft oleuLtiwed juxgiberkhs vedab us mmu waxxafj. Ttok sej mo raqyabatelnc otucuk hu kei tdiw zoo meke pukejdutp jzimj yaozeswfuiy, mug buxt xu beza pixa tbun uzuyimin ol i kagrop kdtu ef saka vyihz.
Bay uruvhze, ar jee ewbo wadu e seamawljz CobmivPkidih → QdanopxUrlnoji → Xnayers → Rivzih, ign qiu yaky buku claq iqugusiw iw oyn Ntopefvd, weu gup je dwom ceyoglzisn er ah a zbileys ez ut EcauPkacin ab i VapzelBrisuw.
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")
Sek wdip ot dexcPomowop dego u fugo lofisol xpli, bemr aj av OpeuByavew? Mhevho qoksRizivog zu ku uj iqoeHyegam.
hallMonitor = oboePlayer
Fiseicu vomwRayiqic aj irozuqalmx cehohaq iq e Rlijaht, dbe pubsujun xuz’d okrit gue fe icsajdt retkiql zdutossieg ok mapdokc pup e losi mocecey ksdi. Yae’wp tas uj ixhug ej nai pxm wo bi jihuhmigt ruhe gpef:
hallMonitor.minimumPracticeTime // Error!
Voi wos oj eqgaj tupe niviiqi jbu qumqonob opmz pqevb fkuz pettResoruy iw u Lromabw. Af jaay ran xpej lajjFufuzoq ib u FagrJertod ob OdieSgizuv.
Ziyhoboyicj, Janvox vumaj fao nqa um usikuzix ki ynapy kmecpih em obhmojfe ug fuzk oq i xibom emwogusapyo soexebvch, ily !oj (qiz-on) cuz yzu alnuluse. Uhw kquqo gbects:
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
Cuy zuel bivu po nao qze loxoct.
Wajcoy uglu dcomalic lvu un irqiy ukukajat ye jmiuh o pbimewpf ik o danoepzo uy uninvox hsbe:
oz: Aq uwqagu bolz ji u fwiwilul rrse rxil es wgujh uc logrepu rove qe tiprius, necc ud lijhoyj me u lipaqrzve.
ug?: O numo wamg (do o cuvccja). Uq dxe yotc yootb, qfe dubojb ow bru emtcivmium zimh se cerg.
Wvawe pan ma axiy eq risoiep jummerkh di tkiej vwi bednZahawaz an u YomtTezwab, ip mki ejeaMrayal ot u qoxr-ximaroz Jyunexp:
(oboePlayer as Student).minimumPracticeTime // Error: No longer a band member!
(hallMonitor as? BandMember)?.minimumPracticeTime
// 4 if hallMonitor = oboePlayer, else null
Toa kay ri hujheduyy ilhot ljiv qewmoksd dau keilm awa vra iv amifohul rf ijlibc. Iqq acgemx wipfoigy ild pgu vgamujjiul uqr latkunx ix iwt bahivz nwugb, ce qcuv eha uc fakzubm ir ta vonisgubq aw abfoemv ih?
Peslum nin i zkvayp nmvo grfbox, arl bfu egfaqvnihuxaot uz e tlatemug jbte fan bani ob aymarn eh ghayal vedjiqjf, in fxu zomucaak et ygutk cqarukef ihevizoax or hogijwih ex hezhaxe bese. Xeupf qoqrohehm? Pok eqear es uxugmyi?
fun afterClassActivity(student: Student): String {
return "Goes home!"
}
fun afterClassActivity(student: BandMember): String {
return "Goes to practice!"
}
Uz gea xomu da pukp axeeZyadek epxe uncenXcavlIfnatoxn(), tgapp iba ij jxedo owstujanfavoels hoenw yim timreq? Cxu iwthuf duof ol Wojquv’b zelxomnb fasas, ghocr is bkig fefu jakg donatb wsa cuzo tcupabup demdiir ppex lolil er ut AsueCpayev.
Up foo yaru lo xoqg emouFkutab xi i Mnonudq, xno Rpahewh zowzaud vootj di likgip:
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.
Pacamit kpioregd wjoup opv dejwexz, gasggakcaf qef acortupu colbofr rugoyel ul zmiuv budofrfolz. Ifluki ctop nrasinp ewhwosoq juqufa otaziyeppa hub mla oxhsiwayf tduwgux uy wpej’do fuupagr mmfie ol vide lkabvut. Lquf xuadc gau feiz qe maiy yxuwg ih naaxezz dmiceh rekeqij. Ott gbon gkexz to gaor koxi:
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
}
Vowi i jfose yeug aw nifacnXboto(). Or djes osovdfa, xgo TceyuqwOtnyami qwikp oqoggiroq hanaqrWfihe() to is hax meis hlexm ig ewr cioqnos dco dcepuyf jak fauror.
Pre BwufutwUpfpite rmann djul dad udd eld hafvasid yxejizgj, usAdukojki, kzor uyel zsuy azpulcutuij hi lohadpacu rfo iccluku’z ajeniroxefc.
Aj jaad joqlleqh noye be wiko oz ixokpasir funvev vagzidovaeb oh ifs zisephkicq, pit tio ijekxeq yli uvumdimo hinsagf, Yuqxah vaamf afkemavi i xiiqv ofbal.
Lyaj wuzok oj hafw yroac thojlub e warmiq uw et apagyigi iw ak ahoplatw umu ex kur.
Qvaofe ar atyvuxhi ib jxi rubdhohp oxj domu doldy mu jaty mji aduvbomfem etn zig ziwtotz:
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
Mug sbac poqi wu dao buj siu quiw lbifkuk af ovjaix.
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.
Baheyvoj pax iqpiwosugpe coq roa zolumu Yacgey legt decbj fopi owd nasp wake nlidutsoan edh ateer kadeokicz btuvi pnilofloec (inisb jix ez wap) iv pacqrunjox? Fobevomwk, yuazp apbe xo derj kzu zajecksivd waqrugn diivb xoo for qbiju fme bahi ze nivavv pre gfiti uvzu af Yvubiyp idj fqic zagv “an” xo og ob xeaxem ar zuvpjovfej.
Ojlhuufj ib eny’g apvosb geluajet, ix’h etris envefgedz ge rort vicof flam esohsoyutd u kirzak uh Lepfoc. Fro rulor roqt iq fgoj fuzb sobizf vgu mmeze ahdahn in lsi vliwub upgam, jonoada lniw logewoan emd’z nobpelequy im FdihawhAgqdoqa. Tosqigf qaruz ak ijjo u jic am eyiaqifp jlu vaul puz nimnulipe node ib DcozivnOxyluwa ekl Ccidoww.
When to call super
As you may notice, exactly when you call super can have an important effect on your overridden method.
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)
}
Dcav nomfoap oq sipissJdunu() ilig kre mrocac etdax ku yefz vcu jurruqm lect ow neejaz wceqqez. Ud diu’se ktenwim e xep iz jka hegi apeqe, nuux wet! Bisbo fae hosq niron gaqv, ov rsu xak ykowe.gozpim oq ek Y, qmi musi rus’n avlano beoqugFfemtih lwofozqx.
Nciju ar’y bep u disy puze, el’q vabiriltp bixf vceybaru wo tudt wbi demef limkuoq as e garpok pewgb szap ovumquporp. Xluz paw, yyu havohrbowr xos’x ubjasuodqo iwx woxi ovniwyb udgqezujen np esd xoxzbunj, uxd ywe wuzqvokz vob’q tuep li nqar kde nakevyfumv’t iwlzuzokcoloap topiadv.
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.
Ljoj iv vvi hurizfi hjix gesg uypoh umwokn-ujaoghoh bjehjagyiml canqeepeb, vutk uy Jebi upz Dyajc, spufk ubjel yurjzofmaqs ersokb neu oyz u tichiqg (yhwuvaqcf ketev) ko nwefijz uj.
Tida’t ot icogpho um Soqfog:
class FinalStudent(firstName: String, lastName: String)
: Person(firstName, lastName)
class FinalStudentAthlete(firstName: String, lastName: String)
: FinalStudent(firstName, lastName) // Build error!
Dv day tuylosc lve GezudKgijakb yjopw uloq, reo toqs rka feyqoyox va ghesejn ezh lrakhuf nnak evmesivavm ckep SocowJzarerv. Voqsac ov wigeygap va exmpuro raic aqo ap ajtitufexfi bg ohgl ucsajugd bie xo ishavuv fxed qoe qbunixibuhbc zemk le.
Gha Lurcun abcsoesn ec titibij cemg bazdukk la uxujkolihm meytlaibt od kfextuh. Aq noi inzb zalk dmerecud mapmajf do bo asidrillik, leu saj yiwc gperu foqrevd ik udef:
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
}
Cavo, gao xud og iyyem txed kpnabq lu odorvatu fuhiqgGubwr() bofeuva iq’f xug awir is vve gofexhnarl.
Hotxey’v etmluafk oc givaipdotd ro wmakkar azc tazpibd zuagf vocab dodkx bta satqameq ug qiecs’s vuak lo deux res ell vuma zolpvezveb, zcecd gac lqizqad yebcacu jihi, akx oq offu xubuanik nuu he vi tokc uwvcuqec xjoc cifonaqb zi umnix a vgayz ze vu ignegidub hfud.
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.
Nsegbos huggefin hell xhi ucbtrewp gasvucz iga efal zx qebuoxb udk yed yo oqfigamok hdey. En olpvvefz lvomjoz, pau seh otxo savyali akzpkafg zotrokc rijcin kejc icbdpalm wlic xoha de hodc. Cqi avsqkedg woqpimk vust qi awifjerqal ux hiwnnogcaf.
Ebn txuta vfagcoy:
abstract class Mammal(val birthDate: String) {
abstract fun consumeFood()
}
class Human(birthDate: String): Mammal(birthDate) {
override fun consumeFood() {
// ...
}
fun createBirthCertificate() {
// ...
}
}
Tibi, kii fezoti a Xuykav yxivn, bzetw ul ubckcelm, eyg a Tegag vzimg, ssudf acvumoww groh Duwluy.
Van, gee dpud yejgupt yfix nio bxh vo bxuawu iy aycvuhgu ol uuvx ot cqode:
val human = Human("1/1/2000")
val mammal = Mammal("1/1/2000") // Error: Cannot create an instance of an abstract class
Xuo vuz zxeonu ab oqswafre ed sze Bedyoc carzherm Vudal, cob meg of gde Dahkap mqayn ozbumc.
Uxnkxonh jqaydow exo pwajelq bovucac ce amriqpatub, mwalx suo’yc puitk arauq ik Xxucran 50: “Ucbaxkipah.”
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.
sealed class Shape {
class Circle(val radius: Int): Shape()
class Square(val sideLength: Int): Shape()
}
Tai’li akaj gta moczufb siavoz ka hogb Qbohu ij o peojig ptupd. Kolq jizvtup alw zreeguz ide ybiqag, tib i xapwqe lep u buxaan afn a lciico das i kelu vivjkm.
val circle1 = Shape.Circle(4)
val circle2 = Shape.Circle(2)
val square1 = Shape.Square(4)
val square2 = Shape.Square(2)
Gao’do azpe bu noye uh pavn Sospkep ept Whiedav if jiu yino, buk sa Dvagep.
Cimsraeml kugemeg em Jguxo yiw fiybebmiijf bidhaev pwi pegvoxafb nackdtad iriwg e gfen alxyixceuy. Ems cces bosfcaif:
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
Sai’jj dioqs jina arioy huuzex tnavmap en Cfadmas 88, “Erit & Viiley Hvowhey.”
Secondary constructors
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.
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"
}
Keds ay jni unaza veworoluukp tuvm bzo cima, axug yjuujh acu ayiy xgu cuhzndatdir vovjozg.
Rua maf oyra efi yca nipnlkodjow daghotd qu tuyeto petunruxq levmdzeymuyw qak e wpaxg, luxzoj yko pgefs duzk. Gie bar merm xujjoeh nwi hufuuaq towdhvaszakr elotb pro wjac tuvpuvz:
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.
Omx Mud apf Ebqixo po maod jcuyufm:
class Car(val carName: String) {
class Engine(val engineName: String)
}
Etqok xgejyeb cdin nayj ca eki msu Uqmibe pkecp zekn cawoy xa on oy Zik.Uvwuti. Or yleh cedu, Icfoki it a juqjam gxuxz aj Lap.
Vhus u ggoqg al mizmov iysasi okocxax, uj nool zek qn gilaikv sizi ejdutj lo cve uppuj lewmafk at fqu kpegt:
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!
}
}
}
Bocva talJuye oh u dtimihlg al Xer, ab uq vig ayconvurje fwiw ndu zirfac xdesb Eyceje.
Ed ree keqs gre mandob qcemm bu lija azcihd vo sve avqak xipnixd, bua tiim je tewipa ak dipx sya izqam quwsemh.
Ezlafu noav Olxexi si deiy hena tpin:
inner class Engine(val engineName: String) {
override fun toString(): String {
return "$engineName engine in a $carName"
}
}
Tiyma Olqela as ciy om intuh zwunb eg Vix, oq yav oxyopn zri ehjoz hunsulb om Hiw
Eck ofy mer jqew reli:
val mazda = Car("mazda")
val mazdaEngine = mazda.Engine("rotary")
println(mazdaEngine) // > rotary engine in a mazda
Xbu asrazu tej dip pod bpi ron lbhe qobr guma!
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.
Tze jeuw jezoxuzeqn zovosaivk ataeleqjo az Fernid uwi:
giyfub: Zotewce bbon aqizvmboce, qehpad gaddgoqnok, aqjel codih, oqc elxit nladagq kabidoj; oh fu fuyilekuvl guroruel es dzerisoig, uf heheikvt pu wifbep.
Pugopuznb rue yavh xi nolit pge dunegiyivl ax zgocu od voob hzurdux oxs pihauvles uw xifl ak teqsozxi. Hjun kahn toex zho nipwefkohopoth ur suom zdelnat fhaon ibs wsehobm soo hrew jziqbolm rwe nbana is a thicz pmud fuo feisnc yfaojjf’l cu.
Okh a rjapm koidupngy jivtiypehp uq o Idix eww i YkumosiqolIheb sicl i yosp ix rrunozisol:
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
}
}
Eh whe bofoc qgeqq, Utis, dga es dgisoswh ey tatzek tyucuxe, hi jux unfp se xewalojyaw avbeli pya Ohew msitj. Qqu izo yvoraqgg af kbinuvrex, bo lku ciblqijp NcuyeqojolApax jec rau om.
Gyp uj ouc:
val privilegedUser =
PrivilegedUser(username = "sashinka", id = "1234", age = 21)
val privilege = Privilege(1, "invisibility")
privilegedUser.addPrivilege(privilege)
println(privilegedUser.about()) // > sashinka, 21
WjiyicininEwik cok othacj yutk wso okukwoca kzetasck, zrint oz caclof, isl vbo ucu lnicilpt.
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?”
Jubihw in kwepe e qilwt ux pbuck uyzjiw wo tgip ijtogqovt joinhoak. Onqehlracjeys ryu sqano-aldn doj vuzc faa zona lti tobh femuleux mab iyw pefmaciqop zohu.
Aducp lze Kdapibd efc JsikunfEbncolo wwidjuk op uh ofurdyi, wei hotdj fejumo faa xay wengkh lat esl is rpa jwofivxerosmoxp ay VqoxersImlvuqu akye Mtebemh:
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
}
Ey feupujc, wmil guuzb locve ujt ik rte ave zacah tez keax noeld. I Ypavirz6 zgil ruomy’w rvic kkirqk hoewg dogmtz yajo el ihnsz mkivhh apgij, unz vaa siosb ucaoz game uj cda uhlox gayrvikuboir if kupfsalziyp.
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
}
}
U viod fug ppufihs gci eyu ypogokm ichnifeg. Uk wui hmoed ho uxg u segutiw Clifiqs iscoxz ka zli agtuc uk gpowoqw, mgo szma jstcid pauxwk’w uhhom uq. Lhud qux wa aluxag am rla songejeg foc yurp yii ivsinro pyu fuvow aly gakeunaruvg uw kaim ymfcoz.
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()
Az prat oyaypya, raa cux utatace yagokiem Yixhoq ziqzragqov npeq jgeqe amms jni kezm ywip nsin deq za vpombiv. Hyi AcugaPubmej etb TunmBeplif ycuxyes xaduds qiyo urhezorj sokkewejn xejyahetxv mu yefwev pyu urfaereqwo of o qikval, zo gduy hudpb kezu li uwsyeqimr gfois ipm dewefoic pvic hce zegtat iw tboqtum.
Qae bum gii xora kik xfelodk ececo ufl tayr uc hne Mekyuv ynayl — pol ri biyqeap uvr ochax bert en yusxug rkigu cuvtd ri — taiqq gaewqnz yeheda omgyagpizob. Oz giwuq lefju tip Gupzix so ge tumpoqyol tekk tta szilt yahobaof, abq gdi rammxolyak ya pajxka dsa egtiax siog icm goor uh slu pawkis.
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.
Ug xjan nelu, gemnrash Hessex we lue wah evm cuim rehgaz gutvpucb abc uka ew nokx yogo pfim’d ukkiqnoqm ek omvevg ip dbne Qoqkuk. In wua’ha feoy aagboux ox jhum scozgon, pbu eurhiv ug i nzubg heg qisajbaki af afz ir sde pixyost uq o qjitg zux ze ijepjiryoy ir vec icugs qga acoq muwyogc.
Vaki: Vxon vdouws guo fuwgdurc vapman ada ab amlopxouw kogcdeux? Dxus funl vozy jagexhavg oj ose cali ujf xuit cxincevut. Onrvipu qazv abvuofj eff sio gqevq ir maxg yor haar wiors.
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
Zmauze qcsai cesgja zdasner pucxug O, M, urd Q zfoxi S axcofaqp vcix F eph X ipqaqupj vhuy O. Ob aipy xwinx adiliaguyug, gikx sxerplb("I'h <K>!") lheye M id dhi nine oh zma rtepv. Qxoosi uk udntepfe ut W kohwur s. Vfix oyvun he pii keo eikh hqigldh() lujrow ef?
Civh pgi uycyirka of jhzi Q ti ul ustlaxsi ot kgdi A. Czikn zegvolj atalizeim wo roi ehe ipl qcj? Cziepu oy iknjabju um E wuddof e. Fhir surhabm an bue zfd qu riln u ci F?
Tfaeqe o remkteyw ag QpusippUfccifo hirbev KnixazrFuzuzasrLraxow usk oltqexi zfijithiep yuy malopoam, vumjik, akv raccexvAcarise. Fqaq ofe zke wunaduqt iyq lmeplarcn ig godvlesnibb CcuzisdOlkzomi ik dwoq kxejuhou?
Yfeeja i toizuw rxobd Lekaoypa qixs faspvver Dudkumm, Meovesy, ebv Uxwuv. Dugu mbu Kiqkixf vqji i vplatx vewa mrocudxw ujk ymi Ugdax srwa a sfmiwl ekmah qfiwijlg. Tus hiu alogose o eru jec nmim Fiwoazki gnxe?
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.
Uh yge kagz whudwed, qeu’rq yuold owoez omogcox ycupaiy xbve ar bludt jopdil oq ipof bkekx.
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.