Share
Explore

Lab Kotlin Inheritance progressive drills

10 progressive drills that demonstrate the nuances and applications of Kotlin inheritance and subclassing.
These examples will gradually increase in complexity and showcase different aspects of inheritance.
DRILL 1:
Basic Inheritance: Create a simple Animal class with a speak() method, then create a Dog subclass that overrides the speak() method.

open class Animal {
open fun speak() = println("The animal makes a sound")
}

class Dog : Animal() {
override fun speak() = println("The dog barks: Woof!")
}

fun main() {
val animal = Animal()
val dog = Dog()
animal.speak()
dog.speak()
}

DRILL 2:
Inheritance with Constructors: Extend the previous example by adding constructors to both classes.

open class Animal(val name: String) {
open fun speak() = println("$name makes a sound")
}

class Dog(name: String, val breed: String) : Animal(name) {
override fun speak() = println("$name the $breed barks: Woof!")
}

fun main() {
val animal = Animal("Generic Animal")
val dog = Dog("Buddy", "Labrador")
animal.speak()
dog.speak()
}

DRILL 3: Abstract Classes
Abstract Classes: Create an abstract Shape class with an abstract area() method, then implement concrete subclasses.
abstract class Shape {
abstract fun area(): Double
}

class Circle(private val radius: Double) : Shape() {
override fun area() = Math.PI * radius * radius
}

class Rectangle(private val width: Double, private val height: Double) : Shape() {
override fun area() = width * height
}

fun main() {
val circle = Circle(5.0)
val rectangle = Rectangle(4.0, 6.0)
println("Circle area: ${circle.area()}")
println("Rectangle area: ${rectangle.area()}")
}

DRILL 4:
Interfaces: Implement an interface Drawable and use it alongside inheritance.
kotlin
Copy
interface Drawable {
fun draw()
}

abstract class Shape : Drawable {
abstract fun area(): Double
}

class Circle(private val radius: Double) : Shape() {
override fun area() = Math.PI * radius * radius
override fun draw() = println("Drawing a circle")
}

class Rectangle(private val width: Double, private val height: Double) : Shape() {
override fun area() = width * height
override fun draw() = println("Drawing a rectangle")
}

fun main() {
val shapes: List<Shape> = listOf(Circle(5.0), Rectangle(4.0, 6.0))
shapes.forEach { it.draw() }
}

DRILL 5:
Multiple Interfaces: Demonstrate a class implementing multiple interfaces.
kotlin
Copy
interface Drawable {
fun draw()
}

interface Resizable {
fun resize(factor: Double)
}

abstract class Shape : Drawable, Resizable {
abstract fun area(): Double
}

class Circle(private var radius: Double) : Shape() {
override fun area() = Math.PI * radius * radius
override fun draw() = println("Drawing a circle with radius $radius")
override fun resize(factor: Double) {
radius *= factor
println("Circle resized. New radius: $radius")
}
}

fun main() {
val circle = Circle(5.0)
circle.draw()
println("Area: ${circle.area()}")
circle.resize(1.5)
circle.draw()
println("New area: ${circle.area()}")
}

DRILL 6:
Data Classes and Inheritance: Demonstrate how data classes can be used with inheritance.
open class Vehicle(open val brand: String, open val year: Int)

data class Car(
override val brand: String,
override val year: Int,
val model: String
) : Vehicle(brand, year)

fun main() {
val car1 = Car("Toyota", 2022, "Corolla")
val car2 = Car("Toyota", 2022, "Corolla")
val car3 = car1.copy(model = "Camry")
println("car1 == car2: ${car1 == car2}")
println("car1 == car3: ${car1 == car3}")
println("car3: $car3")
}

DRILL 7:
Generics with Inheritance: Use generics to create a flexible hierarchy of classes.
kotlin
Copy
abstract class Container<T> {
abstract fun add(item: T)
abstract fun remove(): T?
}

class Stack<T> : Container<T>() {
private val elements = mutableListOf<T>()
override fun add(item: T) = elements.add(item)
override fun remove(): T? = if (elements.isNotEmpty()) elements.removeAt(elements.lastIndex) else null
}

class Queue<T> : Container<T>() {
private val elements = mutableListOf<T>()
override fun add(item: T) = elements.add(item)
override fun remove(): T? = if (elements.isNotEmpty()) elements.removeAt(0) else null
}

fun main() {
val stack = Stack<Int>()
stack.add(1)
stack.add(2)
println("Stack: ${stack.remove()} ${stack.remove()}")
val queue = Queue<String>()
queue.add("First")
queue.add("Second")
println("Queue: ${queue.remove()} ${queue.remove()}")
}


These drills cover a wide range of inheritance and subclassing concepts in Kotlin, from basic inheritance to more advanced topics like sealed classes, generics, and delegation. They demonstrate the flexibility and power of Kotlin's object-oriented programming features.

Focusing on code drills is an excellent way to develop practical familiarity with Kotlin inheritance and subclassing.

Here are some additional code drills for your students to practice with:
Multi-level Inheritance:

open class Vehicle(val wheels: Int) {
open fun describe() = "A vehicle with $wheels wheels"
}

open class Car : Vehicle(4) {
override fun describe() = "A car with ${wheels} wheels"
}

class SportsCar : Car() {
override fun describe() = "A sports car with ${wheels} wheels"
}

fun main() {
val vehicle = Vehicle(2)
val car = Car()
val sportsCar = SportsCar()

println(vehicle.describe())
println(car.describe())
println(sportsCar.describe())
}
Inheritance with Companion Objects:
error

Companion objects in Kotlin are a way to implement static members and methods for a class, similar to static members in other languages like Java. Here's a brief overview:

1. Definition: A companion object is declared inside a class using the `companion object` keyword.
2. Purpose: They allow you to define methods and properties that are tied to the class itself, rather than to instances of the class.
3. Usage: You can access companion object members directly through the class name, without needing to create an instance of the class.
4. Single instance: Each class can have only one companion object.
5. Name: Companion objects can be named, but the name can be omitted.
Here's a simple example to illustrate:
```kotlin class MyClass { companion object { const val CONSTANT = 100 fun staticMethod() = "This is a static method" }
fun instanceMethod() = "This is an instance method" }
fun main() { // Accessing companion object members println(MyClass.CONSTANT) println(MyClass.staticMethod())
// Accessing instance method val instance = MyClass() println(instance.instanceMethod()) } ```
In this example, `CONSTANT` and `staticMethod()` are accessed directly through the class name, while `instanceMethod()` requires an instance of the class.
Companion objects are useful for factory methods, constants, utility functions related to the class, and other scenarios where you need functionality associated with the class itself rather than with instances of the class.

open class Base {
companion object {
fun create(): Base = Base()
}
}

class Derived : Base() {
companion object {
fun create(): Derived = Derived()
}
}

fun main() {
val base = Base.create()
val derived = Derived.create()

println(base::class.simpleName)
println(derived::class.simpleName)
}

info

Abstract properties are a feature in Kotlin that allow you to declare properties in an abstract class or interface without providing an immediate implementation. Here's an overview of abstract properties:

1. Definition: Abstract properties are declared using the `abstract` keyword in abstract classes or interfaces.
2. Purpose: They define a contract that subclasses must fulfill by providing an implementation for these properties.
3. Usage in abstract classes: - Can be used with or without a backing field - Subclasses must override and provide an implementation
4. Usage in interfaces: - Cannot have a backing field - Implementing classes must provide an implementation
5. Accessor methods: You can declare abstract properties with custom getters and setters
Here's an example to illustrate abstract properties:
```kotlin abstract class Shape { abstract val area: Double abstract var name: String }
class Circle(private val radius: Double) : Shape() { override val area: Double get() = Math.PI * radius * radius override var name: String = "Circle" }
class Rectangle(private val width: Double, private val height: Double) : Shape() { override val area: Double get() = width * height override var name: String = "Rectangle" }
fun main() { val circle = Circle(5.0) val rectangle = Rectangle(4.0, 3.0)
println("${circle.name} area: ${circle.area}") println("${rectangle.name} area: ${rectangle.area}")
circle.name = "My Circle" println("Updated name: ${circle.name}") } ```
In this example: - `Shape` is an abstract class with two abstract properties: `area` and `name` - `Circle` and `Rectangle` are concrete classes that inherit from `Shape` - They must provide implementations for both `area` and `name` - `area` is implemented as a read-only property with a custom getter - `name` is implemented as a mutable property
Abstract properties are useful when you want to ensure that certain properties are present in all subclasses, but the exact implementation may vary depending on the specific subclass.

abstract class Shape {
abstract val area: Double
abstract val perimeter: Double
}

class Circle(private val radius: Double) : Shape() {
override val area: Double
get() = Math.PI * radius * radius
override val perimeter: Double
get() = 2 * Math.PI * radius
}

class Rectangle(private val width: Double, private val height: Double) : Shape() {
override val area: Double
get() = width * height
override val perimeter: Double
get() = 2 * (width + height)
}

fun main() {
val circle = Circle(5.0)
val rectangle = Rectangle(4.0, 3.0)

println("Circle - Area: ${circle.area}, Perimeter: ${circle.perimeter}")
println("Rectangle - Area: ${rectangle.area}, Perimeter: ${rectangle.perimeter}")
}
Inheritance with Generic Constraints:
kotlin
Copy
abstract class Container<T : Comparable<T>> {
abstract fun add(item: T)
abstract fun get(): T?
abstract fun sort()
}

class Box<T : Comparable<T>> : Container<T>() {
private val items = mutableListOf<T>()

override fun add(item: T) {
items.add(item)
}

override fun get(): T? = items.lastOrNull()

override fun sort() {
items.sort()
}

override fun toString(): String = items.toString()
}

fun main() {
val intBox = Box<Int>()
intBox.add(3)
intBox.add(1)
intBox.add(2)
println("Before sorting: $intBox")
intBox.sort()
println("After sorting: $intBox")
}
Want to print your doc?
This is not the way.
Try clicking the ⋯ next to your doc name or using a keyboard shortcut (
CtrlP
) instead.