Skip to main content

Kotlin Super Class

In object-oriented programming, inheritance allows a class to inherit properties and behaviors from another class. In this relationship, the class that inherits is called the subclass or child class, and the class from which it inherits is called the superclass or parent class. The super keyword in Kotlin provides a way to reference the parent class from within a subclass.

Understanding the Super Keyword

In Kotlin, the super keyword serves several important purposes:

  1. It allows access to the superclass's methods and properties
  2. It enables calling the superclass's constructors
  3. It helps resolve naming conflicts when both parent and child classes have members with the same name

Let's explore each of these use cases in detail.

Accessing Superclass Members

When a subclass overrides a method or property from its superclass, it can still access the superclass's implementation using the super keyword.

Basic Example

kotlin
// Parent class
open class Animal {
open val name: String = "Animal"

open fun makeSound() {
println("Some generic animal sound")
}

fun sleep() {
println("The animal is sleeping")
}
}

// Child class
class Dog : Animal() {
override val name: String = "Dog"

override fun makeSound() {
println("Woof! Woof!")
}

fun displayInfo() {
println("I am a ${this.name}, but I'm also an ${super.name}")
println("My sound: ")
makeSound()
println("My parent's sound: ")
super.makeSound()
}
}

fun main() {
val dog = Dog()
dog.displayInfo()
dog.sleep() // Inherited method from Animal
}

Output:

I am a Dog, but I'm also an Animal
My sound:
Woof! Woof!
My parent's sound:
Some generic animal sound
The animal is sleeping

In this example:

  • Animal is the superclass with name property and makeSound() and sleep() methods
  • Dog is the subclass that overrides the name property and makeSound() method
  • Inside the displayInfo() method of Dog, we use super.name to access the parent class's name property
  • Similarly, we call super.makeSound() to invoke the parent's version of the method

Calling Superclass Constructor

When creating a subclass, you often need to initialize the superclass's properties. Kotlin requires you to call the superclass constructor explicitly in the class header.

Basic Constructor Example

kotlin
open class Person(val name: String, val age: Int) {
open fun introduce() {
println("Hi, I'm $name and I'm $age years old.")
}
}

class Student(name: String, age: Int, val studentId: String) : Person(name, age) {
override fun introduce() {
super.introduce()
println("My student ID is $studentId")
}
}

fun main() {
val student = Student("Alice", 20, "S12345")
student.introduce()
}

Output:

Hi, I'm Alice and I'm 20 years old.
My student ID is S12345

Secondary Constructors and Super

When a class has secondary constructors, each of them needs to initialize the primary constructor, which in turn initializes the parent constructor.

kotlin
open class Shape {
open val name: String

constructor(name: String) {
this.name = name
}

open fun calculateArea(): Double {
return 0.0
}
}

class Circle : Shape {
val radius: Double

// Primary constructor calling parent constructor
constructor(name: String, radius: Double) : super(name) {
this.radius = radius
}

// Secondary constructor calling primary constructor
constructor(radius: Double) : this("Circle", radius)

override fun calculateArea(): Double {
return Math.PI * radius * radius
}

fun printDetails() {
println("${super.name} with radius $radius has area ${calculateArea()}")
}
}

fun main() {
val circle1 = Circle("My Circle", 5.0)
circle1.printDetails()

val circle2 = Circle(7.5)
circle2.printDetails()
}

Output:

My Circle with radius 5.0 has area 78.53981633974483
Circle with radius 7.5 has area 176.71458676442586

Using Super with Interfaces

Kotlin allows classes to implement multiple interfaces. When interfaces have methods with the same signature, you can use super<InterfaceName> to call a specific interface's implementation.

kotlin
interface Flyer {
fun fly() {
println("Flying like a general flying object")
}
}

interface Bird {
fun fly() {
println("Flying like a bird with wings")
}
}

class Parrot : Flyer, Bird {
override fun fly() {
super<Flyer>.fly() // Call Flyer's implementation
super<Bird>.fly() // Call Bird's implementation
println("Flying specifically like a parrot")
}
}

fun main() {
val parrot = Parrot()
parrot.fly()
}

Output:

Flying like a general flying object
Flying like a bird with wings
Flying specifically like a parrot

Real-World Application: UI Components

In real-world applications, superclasses are often used for creating reusable components. Here's an example of a UI component hierarchy:

kotlin
open class UIComponent(val id: String) {
open fun render() {
println("Rendering basic UI component with id: $id")
}

open fun handleClick() {
println("Component $id clicked")
}
}

class Button(id: String, val text: String, val buttonType: String = "default") : UIComponent(id) {
override fun render() {
super.render()
println("Rendering button with text: '$text' and type: $buttonType")
}

override fun handleClick() {
super.handleClick()
println("Button action triggered!")
}
}

class Image(id: String, val url: String) : UIComponent(id) {
override fun render() {
super.render()
println("Loading and rendering image from: $url")
}
}

fun main() {
val components = listOf(
Button("btn-1", "Submit", "primary"),
Image("img-1", "https://example.com/image.jpg"),
Button("btn-2", "Cancel", "secondary")
)

println("=== Rendering UI Components ===")
components.forEach { it.render() }

println("\n=== Handling Click on First Button ===")
(components[0] as Button).handleClick()
}

Output:

=== Rendering UI Components ===
Rendering basic UI component with id: btn-1
Rendering button with text: 'Submit' and type: primary
Rendering basic UI component with id: img-1
Loading and rendering image from: https://example.com/image.jpg
Rendering basic UI component with id: btn-2
Rendering button with text: 'Cancel' and type: secondary

=== Handling Click on First Button ===
Component btn-1 clicked
Button action triggered!

This example demonstrates how UI frameworks might use inheritance and the super keyword to build reusable component libraries.

Best Practices When Using Super

  1. Don't Overuse Inheritance: Prefer composition over inheritance when possible. Only use inheritance when there's a true "is-a" relationship.

  2. Keep the Base Class Focused: Superclasses should contain common functionality that truly belongs to all subclasses.

  3. Always Call Super in Lifecycle Methods: When overriding lifecycle methods (like onCreate in Android), it's generally important to call the superclass implementation.

  4. Document Override Behavior: Clearly document whether an overridden method completely replaces the parent's behavior or enhances it by calling super.

  5. Be Careful with Property Initialization Order: Remember that superclass initialization happens before subclass initialization, which can lead to subtle bugs.

Common Pitfalls

Accessing Superclass Properties Before Initialization

kotlin
open class Parent {
open val value = 10

init {
println("Parent's value is $value")
}
}

class Child : Parent() {
override val value = 20

init {
println("Child's value is $value")
}
}

fun main() {
Child()
}

Output:

Parent's value is 0
Child's value is 20

Notice how the parent prints 0 for the value? That's because when the parent's initializer runs, the child's value has not yet been assigned.

Summary

In Kotlin, the super keyword is a powerful tool for accessing members of a parent class from a child class. It allows you to:

  • Call methods and access properties from the parent class
  • Call the parent class's constructor
  • Distinguish between implementations in different interfaces
  • Create extensible and reusable code structures through inheritance

Understanding how to properly use the super keyword is essential for effectively implementing inheritance in your Kotlin applications. Remember that while inheritance is a fundamental concept in object-oriented programming, it should be used judiciously, following the principle that a subclass should truly represent a specialized version of its superclass.

Exercises

  1. Create a Vehicle superclass with properties for make, model, and year. Then create a Car subclass that adds a numDoors property. Implement a displayInfo() method in both classes, with the Car version calling the superclass method.

  2. Implement a Shape hierarchy with a calculateArea() method. Create subclasses for Rectangle, Circle, and Triangle. Each should call super.calculateArea() and then print its own calculation.

  3. Create a class hierarchy that simulates a drawing application with DrawableElement as the superclass and Line, Rectangle, and Circle as subclasses. Implement draw() and erase() methods that demonstrate the use of super.

Additional Resources



If you spot any mistakes on this website, please let me know at [email protected]. I’d greatly appreciate your feedback! :)