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:
- It allows access to the superclass's methods and properties
- It enables calling the superclass's constructors
- 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
// 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 withname
property andmakeSound()
andsleep()
methodsDog
is the subclass that overrides thename
property andmakeSound()
method- Inside the
displayInfo()
method ofDog
, we usesuper.name
to access the parent class'sname
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
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.
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.
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:
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
-
Don't Overuse Inheritance: Prefer composition over inheritance when possible. Only use inheritance when there's a true "is-a" relationship.
-
Keep the Base Class Focused: Superclasses should contain common functionality that truly belongs to all subclasses.
-
Always Call Super in Lifecycle Methods: When overriding lifecycle methods (like
onCreate
in Android), it's generally important to call the superclass implementation. -
Document Override Behavior: Clearly document whether an overridden method completely replaces the parent's behavior or enhances it by calling
super
. -
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
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
-
Create a
Vehicle
superclass with properties formake
,model
, andyear
. Then create aCar
subclass that adds anumDoors
property. Implement adisplayInfo()
method in both classes, with theCar
version calling the superclass method. -
Implement a
Shape
hierarchy with acalculateArea()
method. Create subclasses forRectangle
,Circle
, andTriangle
. Each should callsuper.calculateArea()
and then print its own calculation. -
Create a class hierarchy that simulates a drawing application with
DrawableElement
as the superclass andLine
,Rectangle
, andCircle
as subclasses. Implementdraw()
anderase()
methods that demonstrate the use ofsuper
.
Additional Resources
If you spot any mistakes on this website, please let me know at [email protected]. I’d greatly appreciate your feedback! :)