Kotlin Overriding Methods
Introduction
When we work with inheritance in Kotlin, one of the most powerful features is the ability to override methods. Method overriding allows a subclass to provide a specific implementation of a method that is already defined in its parent class. This enables us to reuse the structure from parent classes while customizing behavior in child classes—a key principle of object-oriented programming.
In this tutorial, we'll explore how method overriding works in Kotlin, including its syntax, rules, and best practices, with plenty of examples to help solidify your understanding.
Understanding Method Overriding
Method overriding is a feature that allows a subclass to provide a specific implementation of a method that is already defined in its parent class. When you override a method, you're saying: "I want to change how this method behaves for this particular type of object, but I want to keep the same method signature."
Let's look at the basic syntax for method overriding in Kotlin:
open class Parent {
open fun display() {
println("This is parent class")
}
}
class Child : Parent() {
override fun display() {
println("This is child class")
}
}
fun main() {
val parent = Parent()
val child = Child()
parent.display() // Output: This is parent class
child.display() // Output: This is child class
}
In the example above:
- We define a
Parent
class with anopen
method calleddisplay()
- We create a
Child
class that inherits fromParent
- In the
Child
class, we override thedisplay()
method - When we call
display()
on instances of each class, we get different behaviors
Key Points about Method Overriding in Kotlin
-
The
open
keyword: In Kotlin, classes and methods arefinal
by default, meaning they cannot be inherited or overridden. To make a method overridable, you must mark it with theopen
keyword. -
The
override
keyword: When overriding a method in a subclass, you must use theoverride
keyword. This makes your intention clear and helps the compiler catch errors. -
Method signature: The overriding method must have the same name, return type, and parameter list as the method in the parent class.
Rules for Method Overriding in Kotlin
Let's explore the key rules you need to follow when overriding methods in Kotlin:
1. Methods must be marked as open
class Base {
// This method cannot be overridden because it's not marked as open
fun normalMethod() {
println("Normal method in Base class")
}
// This method can be overridden because it's marked as open
open fun openMethod() {
println("Open method in Base class")
}
}
class Derived : Base() {
// Compilation error: 'normalMethod' in 'Base' is final and cannot be overridden
// override fun normalMethod() { ... }
// This is fine because openMethod is marked as open in Base
override fun openMethod() {
println("Overridden method in Derived class")
}
}
2. Overrides are open
by default
When you override a method, that method becomes open
by default in the derived class. You can prevent further overriding by using the final
keyword:
open class Base {
open fun method() {
println("Base class method")
}
}
open class Derived : Base() {
// This override is open by default
override fun method() {
println("Derived class method")
}
}
class FurtherDerived : Derived() {
// This is allowed because the override in Derived is open by default
override fun method() {
println("FurtherDerived class method")
}
}
// But we can prevent further overriding
open class AnotherDerived : Base() {
// Using final prevents further overrides
final override fun method() {
println("AnotherDerived class method")
}
}
class AnotherFurtherDerived : AnotherDerived() {
// Compilation error: 'method' in 'AnotherDerived' is final and cannot be overridden
// override fun method() { ... }
}
3. Access modifiers
The overriding method cannot have more restrictive access than the method it overrides:
open class Base {
protected open fun protectedMethod() {
println("Protected method in Base")
}
}
class Derived : Base() {
// This is fine - same access level
override protected fun protectedMethod() {
println("Protected method in Derived")
}
// This is also fine - less restrictive access level
// override public fun protectedMethod() {
// println("Now public method in Derived")
// }
// Compilation error: Cannot weaken access privilege
// override private fun protectedMethod() { ... }
}
Calling the Superclass Method
When overriding a method, you might want to use the parent class's implementation as part of your override. You can do this using the super
keyword:
open class Animal {
open fun makeSound() {
println("Animal makes a sound")
}
}
class Dog : Animal() {
override fun makeSound() {
super.makeSound() // Call parent class implementation
println("Dog barks")
}
}
fun main() {
val dog = Dog()
dog.makeSound()
// Output:
// Animal makes a sound
// Dog barks
}
This is particularly useful when you want to extend the functionality of the parent method rather than replace it completely.
Property Overriding
In Kotlin, you can also override properties in a similar way to methods:
open class Shape {
open val vertexCount: Int = 0
open var name: String = "Shape"
}
class Rectangle : Shape() {
override val vertexCount: Int = 4
override var name: String = "Rectangle"
}
fun main() {
val shape = Shape()
val rectangle = Rectangle()
println("Shape vertices: ${shape.vertexCount}") // Output: Shape vertices: 0
println("Rectangle vertices: ${rectangle.vertexCount}") // Output: Rectangle vertices: 4
println("Shape name: ${shape.name}") // Output: Shape name: Shape
println("Rectangle name: ${rectangle.name}") // Output: Rectangle name: Rectangle
}
Overriding a val with a var
In Kotlin, you can override a val
property with a var
property (but not vice versa), as this doesn't violate the contract of the base class property:
open class Base {
open val property: String = "Base property"
}
class Derived : Base() {
override var property: String = "Derived property"
}
fun main() {
val derived = Derived()
println(derived.property) // Output: Derived property
derived.property = "Modified property"
println(derived.property) // Output: Modified property
}
Real-world Example: Building a UI Component System
Let's look at a practical example where method overriding is useful. Consider a UI component system:
open class UIComponent {
open val width: Int = 0
open val height: Int = 0
open fun render() {
println("Rendering a generic UI component")
}
open fun handleClick() {
println("Generic click handler")
}
}
class Button : UIComponent() {
override val width: Int = 100
override val height: Int = 40
override fun render() {
println("Rendering a button with dimensions: ${width}x${height}")
}
override fun handleClick() {
super.handleClick()
println("Button clicked! Performing action...")
}
fun showLabel(text: String) {
println("Button shows label: $text")
}
}
class Image : UIComponent() {
override val width: Int = 200
override val height: Int = 200
override fun render() {
println("Rendering an image with dimensions: ${width}x${height}")
}
fun applyFilter(filterName: String) {
println("Applying $filterName filter to image")
}
}
fun main() {
val components: List<UIComponent> = listOf(
UIComponent(),
Button(),
Image()
)
// Polymorphism in action
for (component in components) {
component.render()
component.handleClick()
println("-------------------")
}
// Specific methods require casting
val button = components[1] as Button
button.showLabel("Submit")
}
Output:
Rendering a generic UI component
Generic click handler
-------------------
Rendering a button with dimensions: 100x40
Generic click handler
Button clicked! Performing action...
-------------------
Rendering an image with dimensions: 200x200
Generic click handler
-------------------
Button shows label: Submit
This example illustrates how method overriding enables polymorphism—one of the key benefits of object-oriented programming. We can store different types of UI components in the same collection and call common methods on them, but each component will behave according to its specific implementation.
Abstract Methods and Classes
A special case of method overriding is when you implement abstract methods from abstract classes. Abstract methods don't have an implementation in the parent class and must be overridden in non-abstract subclasses:
abstract class Animal {
// Abstract property
abstract val sound: String
// Abstract method - must be implemented by subclasses
abstract fun makeSound()
// Regular method
fun sleep() {
println("Zzz...")
}
}
class Dog : Animal() {
// Must implement abstract property
override val sound = "Woof!"
// Must implement abstract method
override fun makeSound() {
println(sound)
}
}
fun main() {
val dog = Dog()
dog.makeSound() // Output: Woof!
dog.sleep() // Output: Zzz...
}
When implementing abstract methods, you're not technically overriding an implementation, but you're still required to use the override
keyword to show that you're implementing the abstract contract.
Summary
Method overriding is a fundamental feature in Kotlin that enables polymorphism and supports the principle of inheritance in object-oriented programming. Here are the key takeaways:
- Method overriding allows a subclass to provide its own implementation of a method defined in its parent class.
- In Kotlin, methods must be marked with the
open
keyword to be overridable. - When overriding a method, the
override
keyword is required. - You can access the parent class's implementation using the
super
keyword. - Properties can also be overridden, following similar rules to methods.
- Method overriding enables polymorphism, allowing you to write more flexible and extensible code.
By understanding and properly using method overriding in Kotlin, you can create more maintainable and flexible class hierarchies, promote code reuse, and build systems that can easily adapt to changing requirements.
Exercises
-
Create a
Vehicle
class with anopen
method calledstartEngine()
that prints "Engine starting". Create aCar
subclass that overrides this method to print "Car engine starting with a roar" and then calls the parent method. -
Create a
Shape
abstract class with an abstract method calledarea()
. ImplementRectangle
andCircle
subclasses that override this method to calculate the appropriate area. -
Create a
BankAccount
class with propertiesbalance
andinterestRate
and a methodcalculateYearlyInterest()
. Create aSavingsAccount
subclass that overrides theinterestRate
property and thecalculateYearlyInterest()
method to provide higher interest.
Additional Resources
- Kotlin Official Documentation on Inheritance
- Kotlin Official Documentation on Classes and Inheritance
- Object-Oriented Programming Concepts
Happy coding with Kotlin inheritance!
If you spot any mistakes on this website, please let me know at [email protected]. I’d greatly appreciate your feedback! :)