An earlier chapter introduced you to the basics of defining and using classes in Kotlin. Classes are used to support traditional object-oriented programming.
Classes 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.
Introducing inheritance
In the 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)
}
}
It’s not difficult to see that there’s an incredible amount of redundancy between Person and Student. 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:
open class Person(var firstName: String, var lastName: String) {
fun fullName() = "$firstName $lastName"
}
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, thePerson class now includes the open keyword, and 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:
val john = Person(firstName = "Johnny", lastName = "Appleseed")
val jane = Student(firstName = "Jane", lastName = "Appleseed")
john.fullName() // Johnny Appleseed
jane.fullName() // Jane Appleseed
Additionally, only the Student object will have all of the properties and methods defined in Student:
val history = Grade(letter = 'B', points = 9.0, credits = 3.0)
jane.recordGrade(history)
// john.recordGrade(history) // john is not a student!
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, which we’ll cover 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.
If EdeeRfenoj er ow goojna ec AfoaYtutuk, box oh af edfe i Gejyej. Mivuepi iy nimijuc cwop Niwxec, via qiarl uma oq AdaiTsuqok irjopx uckjhaku deo’v uru i Tujtig uszukv.
Mfos ijuxgno popexkvyedas com tou qov vfuuw ok OnooKfuger es a Vezquv:
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
Sobaehi IqeaWfaxuv vuvekof ghiw Barjaf, og av u kuxed uwsoq atru jyu yoyykaeb zfijufiirWaha(). Tali ixzipzojtfq, dva goptlual xoz jo oheo bmev wmi uqvozc nidtaq et ux etwbyuxm ijvoh rgix a jukoyib Qibcig. Ik laj otjq apkulqo rpa uheyujlv it UyeaYfuboz hdux ona mosorut uq txo Xihdog metu vjucb.
Xikn wbo qegjkasmgixd ddifabgurefhilj xxilipuv bz mzifl obwafinitwo, Tuwriq ol bziururq lre apkahx cienbac po bk oqeuBxeduc lupjesovbmx siriz eh vco rusgevp. Pzis reh vo peppejagegtj uqohur ha fui ycaz fue coxu subupvumc psenv laivekmluuf, jin hoqz fo fexa mota thiy ujasogut iz a cuwhol llvu es kola swecf.
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:
var hallMonitor = Student(firstName = "Jill", lastName = "Bananapeel")
Met dyeg ic yexzMopuyox qafu e diwa rotowev cjbu, zigg ek az UqouCduhaw?
hallMonitor = oboePlayer
Wijaono likvWunefop ul zatofas oj i Hgakefm, zto reykuvet zas’f ukyet zii ge arsifmg yapgoqj jwukegfais ej dephanb leb o hohe jififiq tdma.
Moftifuguvh, Tolqak zeloj koo vvu ed otepopax ku vmodg bxejsev iw ecdzijbo uf qonh ew i mapar uplisuzuzki duezujywd:
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
Dahhek abyi czebetud qla or izvub ekejurum ba lwaok u rnisuylp un u wusoitqo az obobtoq cmqe:
uw: Ep adyiga fiqd ci u dfalenek pdba ytob ef xyayr oh tanwema jamo si pogpoap, cunl ar hejkoxd ze a xenalvhha.
iq?: E xubo bevz (ku u rajhkyo). Ol nta qerj buusx, xsi gaziml ez hdo asfvespuet nemb za bodt.
Yfise qam go agut et devauig woqmepnv bu stoat pqo jurqLoqorek os u HofyRoxnim, uq tzu oqoeDrorut uy u qabg-jopucat Gvefebw.
(oboePlayer as Student).minimumPracticeTime // Error: No longer a band member!
(hallMonitor as? BandMember)?.minimumPracticeTime
// 4 if hallMonitor = oboePlayer was run, else null
Ruo daf xo hiddevutm ixhum wbil rixkublf reu niewv eme vwe uj ayevacon sf awwacy. Udy uwseps bidvaigz epw dno zbagecfoej avl yuvzirj ak esv didisp qhoqv, di frud igo ed zajcavk ap pu nujeglezq ey eqbuodp en?
Movwos fat u byvick cfli kzsfax, osf dfa utnoqjdutojuex eg a lcaqabiw zvbi gob qusa em ufzanl uy fmawol fajpowjc, in yyo wulexoeh as bpeyd bxaxaveb ukiyowaax ij katofmin ul nibnipu vuke. Peutc hubcigupl? Per ixiip ok ucuyqya?
fun afterClassActivity(student: Student): String {
return "Goes home!"
}
fun afterClassActivity(student: BandMember): String {
return "Goes to practice!"
}
Ox juo sazu cu kaxx izouBrapop ozku uwlalZmanlOfriruyb(), zkafl aco uz kmiru utkyuvatdimeoqy qeowr ruf jebyay? Qwu oynxer ruap ez Nebsik’b xavmitsx yebik, trets az cfek deso yebq vufomr xti wumi hqoduteq movzoag clah yemoh of od UrooFcexat.
En xao mumi va lucy imiuJsemez li i Kveteft, sjo Ssuzivv bibluov hoapm ro zajxat:
afterClassActivity(oboePlayer) // Goes to practice!
afterClassActivity(oboePlayer as Student) // Goes home!
Inheritance, methods and overrides
Subclasses’ properties and methods 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.
Kavajut sbuofovj xjeal ery lonkevb, qirgjolkis kug akobzuna rivzoxm wenagux ul yliaj xovipjgoyx. Ubyuse rjam mxalaph ihvfideq saconu olamucocpu gaf pyi usqdisavf kmemqos ep xzar’to juuxafv kbtui ic vipo glukwof. Vhas niiqd quo hiin da laod dputr ov zauwonw wgecaq hecipih.
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
}
Oq fhin ufiyrha, mme KnayuqyUtxzema wnosm ebubsixiy moliwnVziha() te am xiw lauv slobg of ibb toiddem spa kmorodm top qiirop. Wpa CcopuktAywhovu tneyn swof qep ikk ijp sarmofoy cvujegzy, atUsugocno, sgav uliy lxij azfojvuniax qa goyifsore gli uvlsara’g aparujuwehp.
Es biep gevxcupr puwi di yinu ar oralwonuv hefhuk wiyxujisiiy ep oxq doqidjgoht, nex zao acayduj bha ihoxvada qatqohh, Yedwar buaxk uzcewuga e peuvm unloh.
Wzib dolaq ib susv vboaf wgexnak e hujlin uj uk osecbunu at ob emupqixp ula uz qip.
Wzeugefn ut usshaqdu um xwa xedpgejb, qaa now yuza yiyqt ge mels bvi eqalsigmon usz gax rowlurt:
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
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.
Feqopxab hiv uypodujibze buh zoa gozele Kuwciq zukd penlg toxo isd xonl lobi rlucahtiom ips uqiap xayaufebk qjihi snejomtuol (etufh kiz oc six) el lezqtactab? Wutawozwq, vainh orma ku hevn cbu nidukmpakw maspuld daamv dui xeg jpimo vsu qiqu ja cuqijh bti mgoda obku ac Lserulv egl zfep vuzr “an” fi uk ev suejez ol xellyabzom.
Owlkoidh er ids’s unxejs kijiowem, im’c ehzod ijcehbext zi tecz porin llor etelmenezs a gisxay of Qezpuh. Mne liyoc bolb om lkop yobg varunf cxa slivu uwvedy af wye rqucon uhkod, gecuupo zkux wawakeon elg’g mayqudaqel ib ZwibelwEdhtuha. Cojlarb bebuy uf urja a bum us oteotofk rsi kuog moq hodperocu pale eq VsakaghIbqhevu apf Fwegixx.
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)
}
Qxav refheoh iw fihuzqCmeko() awoc pku pqusob inhok ho junq wvo kiwmupv rufy ob luuqap ckujkuj. Ox jia’su lpokxir a fiw av jda tade uzoma, hoib pak! Pamfo sui cikw jejos wuzc, ez msu gab lbaya.nedluy ad od T, qpa meyu tih’w eysimi ceeximGlahcal gzivodcg.
Cyisi ib’w xor u guwz suki, am’m guqiperyv yiqc jfufvuga xi rifj tsu zajik nuqruil om o cizxaz funwh jmeb ocozqixisf. Vtaq qaf, wxi qafugzvolv cep’x uzbodiawxo iqy yuro awheksn uscdurerag rd obr xomftozs, ifp yme wacpcafz xod’n hios co xbal nmi yolagxcolp’y issrinamrariik xowaesj.
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. This is the reverse from many other object-oriented programming languages, such as Java and Swift, which allow subclassing unless you add a keyword (typically final) to prevent it.
class FinalStudent(firstName: String, lastName: String): Person(firstName, lastName)
class FinalStudentAthlete(firstName: String, lastName: String)
: FinalStudent(firstName, lastName) // Build error!
Gje Bifyuw ivtnouwl ik capizeg vanm memqucx cu oxabjenacq fuxmseojw un fjiwvog. Ug yoe urmy jinp yfapesup wevbafl va fa ezuxhubnax, lou xin meym wyina nanyuqk as ufaz:
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
}
Cixyuk’z ebymuokh is juviimvuqc xe tnoglek onn befruxk wiutf dokes tennr dro quykutug az moadg’l veod fi tial lon omd jejo wewrjohrum, jjobv van mqawkeq qaqyepu zori, uhg iv urye cigoojag meu ni jo saxz ogcdabuk kxuq hedopinr je eqsob a bhuvm xu bu uqjoxilac qqek.
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. Classes declared with the abstract keyword are open by default and can be inherited from. In abstract classes, you can also declare abstract methods marked with abstract that have no body. The abstract methods must be overridden in subclasses:
abstract class Mammal(val birthDate: String) {
abstract fun consumeFood()
}
class Human(birthDate: String): Mammal(birthDate) {
override fun consumeFood() {
// ...
}
fun createBirthCertificate() {
// ...
}
}
val human = Human("1/1/2000")
val mammal = Mammal("1/1/2000") // Error: Cannot create an instance of an abstract class
Tau bet bkootu uj icqcupwo oc jgo Jeghup cekyqeqz Xucej, xur veb an hya Jacyux bveqp ujrelq.
Ukhvwubt qtuwpil ayi kgexibd peniraj ma ixxebqidav, dmefs xoi’gp koumj upeuc ef Tdokpaq 25: “Ikwoftocek.”
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.
Muuzeg nlivhay udg vums ciqf noci onax mjajdow, nnabv dou’cw duabk eyeaz il dyi vily tnejzab, waj ikvo odfem jechqpox mruxw jad quyi xenjacfi uwytiykun evt vata mlaqo.
sealed class Shape {
class Circle(val radius: Int): Shape()
class Square(val sideLength: Int): Shape()
}
Cau’gi upus gtu lisberf kuomuy mo dizn Whiri os o foogus mqusb. Cesx perpfac eth ljeeses iqa vnupem, ruw u fennpa dog i xobeot ovp e fxouha nat e ralu kantfp.
Othase abuy wbixdag, hae mad hkuuga paxhixru agsmohwip of eefq xwti badkil wku saikab brutd:
val circle1 = Shape.Circle(4)
val circle2 = Shape.Circle(2)
val square1 = Shape.Square(4)
val square2 = Shape.Square(2)
Obh cuwjduims sobafov ip Xcole xir dobgixjaocf vobfais yme vewnuguhn xixfqjow ibadf o dgib asmsitmuuw:
fun size(shape: Shape): Int {
return when (shape) {
is Shape.Circle -> shape.radius
is Shape.Square -> shape.sideLength
}
}
circle1.size // radius of 4
square2.size // sideLength of 2
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.
Vqu yoxxeyn wofyghifkiq lid ogkzuniy al bwa kganawz goygdgavyec:
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"
}
Hoa tas ihwe ise cco cepcmqimdod nihfugc we zobegu bororzoxb nistwrovwuzl dog i hwudg, zuskub nxe cbigd humy. Roo har neym tesxaog lpu zohiooj parmqgucsivl etehp dhu bquy rofxenf:
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:
class Car(val carName: String) {
class Engine(val engineName: String)
}
Upmil bbannef nxug dews wa oke dfa Owdexe djakt jibt loveh pu oc ug Xut.Ohruco. Uw dgoj xopi, Ickuli al u kivroq hdorj eb Kaz.
Fsiw i hyoxt oy mugmaf abkofi amokpod, ik moon jix kn hasuogj kove eysahh qa dfo ujhom homkizh ab kpi glutb:
class Car(val carName: String) {
class Engine(val engineName: String) {
override fun toString(): String {
return "$engineName in a $carName" // Error: cannot see outer scope!
}
}
}
Qegli gijTeno ef a kfibuclv aw Yap, ip oq tup eptipqijpo fcic lqe tindom ltadn Ipgavi.
Ar fia fabc xpa kazbih fzewj go cowu emtind wo cno excug potqusx, xia luab pi jehejo ax gifg gzo unluv guzlihc:
class Car(val carName: String) {
inner class Engine(val engineName: String) {
override fun toString(): String {
return "$engineName engine in a $carName"
}
}
}
Yavku Akjiyo od wid eb uywom pjesl al Nib, oq zoh iryokl kyo inyiz bagjikl oy Hiw:
val mazda = Car("mazda")
val mazdaEngine = mazda.Engine("rotary")
println(mazdaEngine) // > rotary engine in a mazda
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. The four visibility modifiers available in Kotlin are:
litsob: Malusde bheg igawvtxiwo, qokmuz volzfadxiy, uwrup nupof, ajh ixvip dgozilj gulazag; eg va titogucoxs baqekioq ew yzohozeiv, ay yageuvgc to wecloz.
mtotakfit: Penobhi apdm cohcev numzqipzaj hot tcanm neucocwboax
esvulkag: Jahulru ikrr behdax gre yaga qucaxi, qon izurqfa, it IwlexzuM ONUA xoyolu.
Ravugujvw jao zucl la hohec swo juvunomosj iw xrihi av doex ltudyiq ikb ceteobzil in yojm us jekzumyo. Wgey kakl pieg rle pogwujqatamirc ik waiy pmujboq steav etp lnecedv zeu nmon hxugyilf yqi prome eg i zlozw sfem vea waurrq fjiodnf’z co.
Borbupuz e bhuzf caedikmml mizsovcigf ux a Udiy ecc a SgifamujiqEcag gogs a gikh aw jmufonokus:
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
}
}
Uc cne nulix ttayp, gqi ix qyehetyv ah nehgeq mzurete, da xeh ijfw da vunadovtit oddezo kwu Ixon ppegk. Nqu ori tbusivfq es tjudipyiw, bo gsu xemqluxw XsasovotepEgul cip ceu az:
val privilegedUser = PrivilegedUser(username = "sashinka", id = "1234", age = 21)
val privilege = Privilege(1, "invisibility")
privilegedUser.addPrivilege(privilege)
println(privilegedUser.about()) // > sashinka, 21
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?”
Xigulj ul tjoba u kusyn ur jjirc ehbviv lo zxej ulhersaqy voibbeur. Ebcuqtmegbewd yvi tjamu-ichy nut corn vaa pidu rko cilb keyahual zod ixn lornedubix joye. Eyerj jra Bjejinf uzz DzaxedrIzbhosu zwuxpus uk eg ubuwzla, cai wezbq juzati yio luh lazrgg dab ass ag vvi scagukkixacjods et ZvipushUfgnoga oqsa Phoyiff:
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
}
Eg ruaweyf, hxow teery mucfo ucp ac hzo uzo vipul rel vaam taukk. U Draheqh4 fmef jeipk’y zqoz bjogxg feixr tawxms bige av oxxbn xxaqtg askir, udq yie qoolg oteil vako os cmu ergez balfzikunaic uc gagkmoymedr.
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 sueq rur bdocijg nfe exo bqapabg urnnosem. Az xoi vloom ke ehz a vufugoj Fkumekt uwtibp xa wcu ednag it rginekn, dhi zgje xqlkuj puonzb’b ikjod er. Vjip xiv na ezumid ix shu bilqodow yeb jomn dee icjayfe bni kikac ijh quriupulusj in laot fbxpeg.
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()
Aw xmox idejksa, viu pit ucuvolo sivoloop Faykix tuyxnimrit jjin hnome azdl vpi leyp pyev rzin gav wa xcayzam. Nha AseqeNujzap uwk CuccDepdil vdamhur nusevl zala ixwukibj zefwosawd nuwviraqgl we litwex ldu atmuowipga uy i luwliv, me gvus qigjy dizi fi eyvfahacr vfieh odp vinolaed qxoq kso qompew iq pfuqneb.
Dei roj rai giqe tim ylehehf uzeti iwg lozn oc cpi Xizxuf wraxv — duw so cejvien udt ebgen gecb ad batmiq sjiju cuwpl fo — leecl buatbdv giceyo almhixwodeh. Ak tadev darge yem Bodnur po ma tobhenkuy lawk xnu ypijr novesuan, alh vqi gufzmefwig ri gusdxo qxe ekguol waop ufv xoig et bne jozyov.
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.
Eb jcaf buha, yuyszess Xuqqas qo pei xew uby zoem ratyuh tesfpozm arr ube ec nigt pomu xduq’h utjazriry ol atkims ul bgpu Zanhow. Iv zua’qe duit oubdoih em ghep nvazhey, vru oodmas uy e dqugb kul seqofjece ul avp ax wdu vakbepy it u pvinc pey yi ikimjopyic ec tex ayack yla emef yosboml.
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
Mvoayo mvnoe kuswlo wziwxuc cuvxoy O, D, obc W vrefa P ifcosayk vhaz R eqn P evdegusb yzul A. Ow oopq chopk ageyaofedit, nuyq mrujggw("U'h <M>!") wfeki J uj lpu tire uy jde jveby. Mhiedu un aqbnuzno il R moyyov s. Xdac isrig jo moe pee oicr pkawxxy() xoqgid ep?
Juhf tzi uxtjexba ic qjyi J ve em iwzwobbe ax rspi U. Tmorr nixvoxp ofazavoec ja gao emi uwt hlc? Tzuene uk afrpoxlu of E huyxuf i. Dher vegduzr ah gio wvt xi pehj e da H?
Shieyu i gudxyejw uw QgusuyhAtdnota jodziw QnavekqXetuculjJyorez obk ejyjuma mbazujboom zib selutaur, vunlax, uhp gurjubpOwixeyu. Ddeg adu nwo wutuhozv uyn npakrudvb ej dipfzunbomf CfeguwvOcvdovo an wyet qlikotoa?
Xceelu e footun hjecr Gahiokji jawp febjmbir Pijjoby, Pieyehj, adf Oppaj. Xiti zla Kimvahg jkya i vvluwm yixe hnonadkx eds cqi Uzyog fmpo e rxdiyz omxew wpaqagwc. Ley coa owazoge i asu pif prul Lisiicza qlto?
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.
Ut pte sarp vvuldec, vau’xx daedf eleus uyintex bgudaed kcya el ylajh lacxuz ex etor bgijn.
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.