In the lesson, you learned how to define subclass, which extends the behavior of the base class. Now, it’s time to see how this works in reality.
In the demo, you’ll reuse the base class Food and subclass Fruit:
open class Food(
val name: String,
var price: String,
var origin: String) {
fun label(): String {
return "$name of $origin. Price: $price"
}
}
class Fruit(
name: String,
price: String,
origin: String,
val stone: Boolean = false
): Food(name, price, origin) {
fun hasStone(): Boolean {
return stone
}
}
Say it one more time: Fruit is a Food. What can you do with this statement:
fun main() {
val otherTomato: Food = Fruit("Tomato", "3.0", "UK")
}
The otherTomato is of type Food but is represented by the instance of class Fruit. It’s entirely safe to access all the properties of the Food class because you know 100% that Fruit also has them by definition.
But can you somehow access the properties of the Fruit class? Or at least know that the instance used is of type Fruit?
The answer to this question is yes. Kotlin has the operator is, which is used specifically to check the type of the instance assigned to a variable in the runtime:
println("otherTomato is Fruit: " + (otherTomato is Fruit)) //otherTomato is Fruit: true
println("otherTomato is Food: " + (otherTomato is Food)) //otherTomato is Food: true
Obviously, you can reverse the check with the !is operator and find out if the variable doesn’t hold the instance of a certain type.
OK. You figured out that otherTomato is Fruit. Now, you can use this knowledge to your advantage. If you need to assume type to access relevant properties, you’ll use the as infix operator:
println("otherTomato as Fruit has stone: " + (otherTomato as Fruit).hasStone()) //otherTomato as Fruit has stone: false
Since there’s no guarantee that the type is identified correctly, you should use the as? operator that returns null if the assumption about the type is wrong. The null-safe code would look like this:
println("otherTomato as maybe Fruit maybe has stone: " + (otherTomato as? Fruit)?.hasStone()) //otherTomato as maybe Fruit maybe has stone: false
The result would still be false, but if the otherTomato was instantiated as Food or, for example, Veg, the application would still run but return null.
The runtime hierarchy check is a very powerful tool, but it should be used with discretion.