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:
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())
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)
}
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)
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() {