Is-a relationships
Many things are kinds of other things: a puppy is a dog, a car is a vehicle. Inheritance models that "is-a" relationship in code. A child class (subclass) gets the parent's (superclass's) properties and functions automatically, and adds its own. Instead of copying the same code into two classes, you write it once.
Kotlin classes are closed by default
In Kotlin you cannot extend just any class. A class is final unless you mark it open. This protects classes that were not designed to be extended:
open class Dog(val name: String) {
fun bark() {
println("Woof! I am $name")
}
}
Without open, the line class Puppy(name: String) : Dog(name) fails with this type is final, so it cannot be extended. The lesson's starter code already has the open.
Declaring a subclass
A colon after the class header names the parent, and the parent's constructor is called right there:
class Puppy(name: String) : Dog(name) {
fun whine() {
println("whimper")
}
}
The part : Dog(name) does two jobs. It says that Puppy inherits from Dog, and it passes the name argument to Dog's constructor. A parent's properties must be initialized, and the parent's constructor is the code that does it. If you write only : Dog, the compiler reports that the type has a constructor and must be initialized.
Notice that the parameter in Puppy(name: String) has no val. It is a plain constructor argument that is forwarded. Writing val name again in Puppy would clash with the inherited property, and the compiler would say that name hides a member of the supertype.
What a subclass gets
- Properties. A
Puppyhasname, declared only inDog. - Functions. A
Puppycan callbark(), declared only inDog. - The type. A
Puppycan be used wherever aDogis expected:val d: Dog = Puppy("Buddy")compiles.
The reverse is not true. Through a variable of type Dog you cannot call whine(), even when the object is a Puppy, because the compiler only knows about what Dog offers. The error is unresolved reference 'whine'.
A subclass can also take extra constructor arguments of its own, passing only some of them on:
class Puppy(name: String, val ageMonths: Int) : Dog(name) {
fun info() = "$name is $ageMonths months old"
}
Puppy("Buddy", 3).info() returns Buddy is 3 months old.
One parent, many children
A class can have only one direct parent class, but a parent can have many children. Puppy and, say, Hound can both extend Dog, and each gets bark() without repeating it. A change to bark() in Dog then reaches all of them. Every Kotlin class also has the root type Any above it, which is why every object already has functions such as toString().
Construction order
When you create a subclass object, the parent part is built first. If both classes have init blocks, the parent's runs before the child's. For a Dog that extends Animal and prints from each init, creating one dog prints Animal Rex created and then Dog created. This is why the parent constructor call is part of the subclass header: the parent's properties must be ready before the child's code starts.
The exercise
The starter code has the open Dog and a mainFunction that creates a Puppy named "Buddy", calls bark() and then whine(). Step 1 declares Puppy with : Dog(name). Step 2 adds whine, since bark is inherited:
open class Dog(val name: String) {
fun bark() {
println("Woof! I am $name")
}
}
class Puppy(name: String) : Dog(name) {
fun whine() {
println("whimper")
}
}
class Solution {
fun mainFunction() {
val buddy = Puppy("Buddy")
buddy.bark()
buddy.whine()
}
}
The output is:
Woof! I am Buddy
whimper
Common mistakes
- Forgetting
openon the parent. The subclass will not compile. - Omitting the parent's constructor call. Write
Dog(name), with parentheses and arguments. - Adding
valto the forwarded parameter. That redeclares the inherited property. - Redefining
bark()inPuppyto "reuse" it. It is inherited already. Changing what it does is the next lesson, overriding. - Inheriting when "has-a" fits. A dog has a name, but is not a kind of name. Use inheritance only for true is-a relationships.
Key takeaways
- A subclass inherits properties and functions from its parent and can add more.
- Parents must be marked
open; classes are final by default. : Dog(name)declares inheritance and calls the parent's constructor.- A
Puppycan be used as aDog, but through aDogvariable onlyDog's members are visible.