Changing an inherited function
A subclass inherits its parent's functions. Overriding is what you do when the child wants to change one of them. A puppy is a dog, but it sounds different: Yip! instead of Woof!. Rather than invent a new function name, you redefine bark() in Puppy. Code that holds a dog calls bark() without caring which kind it is, and the right version runs.
Two keywords, both required
Kotlin makes overriding explicit on both sides:
- The parent marks the function
open. Functions are final by default, like classes. - The child marks its replacement
override.
open class Dog(val name: String) {
open fun bark() {
println("Woof! I am $name")
}
}
class Puppy(name: String) : Dog(name) {
override fun bark() {
println("Yip! I am $name")
}
}
The signature of the override must match the parent's: the same name and parameter types. There is no annotation to add. override is a keyword, and the compiler enforces the pairing.
Both sides of the contract are checked. If you leave out open, the override fails with 'bark' in 'Dog' is final and cannot be overridden. If you leave out override and just write fun bark() in the child, the error says that bark hides a member of the supertype and needs the override modifier. If the signature differs, as in override fun bark(times: Int), the compiler reports that it overrides nothing. These errors are the point: you cannot override by accident or by a typo.
Which version runs
The version is chosen by the actual class of the object, not by the type of the variable:
val buddy = Puppy("Buddy")
buddy.bark()
val d: Dog = buddy
d.bark()
Both calls print Yip! I am Buddy. Even though d is declared as a Dog, the object is a Puppy, so its own bark runs. A plain Dog("Rex").bark() still prints Woof! I am Rex.
Extending instead of replacing
An override can also build on the parent's version. super refers to the parent:
class Loud(name: String) : Dog(name) {
override fun bark() {
super.bark()
println("(louder)")
}
}
Loud("Max").bark() prints Woof! I am Max and then (louder).
Overriding versus overloading
These are different things. Overriding replaces a parent's function with the same name and parameters. Overloading adds another function with the same name but different parameters. If Dog also had fun bark(times: Int), a call such as bark(2) would pick that one by its argument, and Puppy's overridden bark() would be untouched.
Overrides stay open
A function marked override is itself open, so a grandchild can override it again, as long as its class is open. To stop that, write final override fun bark(). Overriding it further then fails with the same is final and cannot be overridden error.
Overriding properties
Properties follow the same rules. A property can be open in the parent and override in the child, and functions that read it see the child's value:
open class Dog(val name: String) {
open val sound: String = "Woof"
open fun bark() {
println("$name says $sound")
}
}
class Puppy(name: String) : Dog(name) {
override val sound: String = "Yip"
}
Puppy("Buddy").bark() prints Buddy says Yip, although bark itself is written only in Dog. Here the child changes data, not behavior, and the parent's function picks it up.
The exercise
The starter code gives Dog with an open fun bark() and an empty Puppy. The first step declares override fun bark() inside Puppy, and the second makes it print "Yip! I am " followed by name. The property name is inherited from Dog, so a string template can use it directly:
open class Dog(val name: String) {
open fun bark() {
println("Woof! I am $name")
}
}
class Puppy(name: String) : Dog(name) {
override fun bark() {
println("Yip! I am $name")
}
}
class Solution {
fun mainFunction() {
val buddy = Puppy("Buddy")
buddy.bark()
}
}
The output is Yip! I am Buddy.
Common mistakes
- Forgetting
open. The parent's function must allow overriding. - Forgetting
override. A same-named function in the child is an error, not a quiet replacement. - Changing the parameters. A function with different parameters is a different function, so it cannot carry
override. Defining it without the keyword is overloading, which is allowed. - Misspelling the text. The output must be exactly
Yip! I am Buddy, with the capital Y, the exclamation mark and the name after the space. - Calling
super.bark()by habit. It would print the dog line first and break this lesson's expected output.
Key takeaways
- The parent writes
open fun; the child writesoverride funwith the same signature. - The object's actual class decides which version runs.
super.name()calls the parent's version from inside an override.final overridecloses a function to further overriding.