Skip to main content

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:

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:

  1. We define a Parent class with an open method called display()
  2. We create a Child class that inherits from Parent
  3. In the Child class, we override the display() method
  4. When we call display() on instances of each class, we get different behaviors

Key Points about Method Overriding in Kotlin

  1. The open keyword: In Kotlin, classes and methods are final by default, meaning they cannot be inherited or overridden. To make a method overridable, you must mark it with the open keyword.

  2. The override keyword: When overriding a method in a subclass, you must use the override keyword. This makes your intention clear and helps the compiler catch errors.

  3. 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

kotlin
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:

kotlin
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:

kotlin
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:

kotlin
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:

kotlin
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:

kotlin
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:

kotlin
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:

kotlin
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

  1. Create a Vehicle class with an open method called startEngine() that prints "Engine starting". Create a Car subclass that overrides this method to print "Car engine starting with a roar" and then calls the parent method.

  2. Create a Shape abstract class with an abstract method called area(). Implement Rectangle and Circle subclasses that override this method to calculate the appropriate area.

  3. Create a BankAccount class with properties balance and interestRate and a method calculateYearlyInterest(). Create a SavingsAccount subclass that overrides the interestRate property and the calculateYearlyInterest() method to provide higher interest.

Additional Resources

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! :)