Inheritance

As with many object-oriented languages, Kotlin supports class inheritance. A subclass inherits from a superclass, and what it “inherits” are all of the properties, functions, constructors, and other stuff that the superclass makes available.

What Happens By Default

The various versions of the Foo class shown above do not inherit explicitly from anything. The default base class for a Kotlin class is Any, and so Foo is said to derive from Any.

Extending a Class

In Java, we use the extends keyword to denote inheritance:

class Foo extends Bar {

}

Ruby uses the < symbol:

class Foo < Bar

end

Kotlin is a bit closer to Ruby syntax, but it uses a colon as the symbol:

class Foo(var count: Int) : Any() {
  fun something() {
    count += 1
    println("something() was called $count times")
  }
}

fun main() {
  val foo = Foo(0)

  foo.something()
  foo.something()
  foo.something()

  println("the final count was ${foo.count}")
}

Here, we are explicitly declaring that the superclass of Foo is Any.

Chaining to the Superclass Constructors

The () at the end of Any() in the above example is actually a call to the superclass constructor. You will need to supply parameters to that call, if the superclass requires them:

open class Base(val tag: String)

class Foo(var count: Int) : Base("Foo Example") {
  fun something() {
    count += 1
    println("$tag: something() was called $count times")
  }
}

fun main() {
  val foo = Foo(0)

  foo.something()
  foo.something()
  foo.something()

  println("the final count was ${foo.count}")
}

Here, Base has a constructor requiring a tag parameter. Foo needs to supply that parameter when it calls the Base() constructor. But then Foo can reference the tag property that it inherits, including it as part of the printed output:

Foo Example: something() was called 1 times
Foo Example: something() was called 2 times
Foo Example: something() was called 3 times
the final count was 3

Open Classes

You will notice that the declaration of Base has something beyond a constructor parameter: it is prefixed with the open keyword.

In Java, by default, any class can be extended by a subclass. To prevent a class from being extended, you have to mark it as final.

Kotlin goes the opposite route. Kotlin classes cannot be extended by default. You have to mark classes that can be extended using open. Failure to do that results in an error. So, if we remove the open from the previous example:

class Base(val tag: String)

class Foo(var count: Int) : Base("Foo Example") {
  fun something() {
    count += 1
    println("$tag: something() was called $count times")
  }
}

val foo = Foo(0)

foo.something()
foo.something()
foo.something()

…we get a compile error:

error: this type is final, so it cannot be inherited from
class Foo(var count: Int) : Base("Foo Example") {
                            ^

This has an important side effect: you cannot create subclasses of arbitrary classes that you do not control. Only if the implementer of that other class is willing to support subclasses will the implementer use the open keyword, and only those classes can you extend.

This approach is uncommon in major programming languages. Basically, Kotlin recognizes that inheritance is a potentially fragile relationship between two classes. Ideally, the classes adhere to the Liskov substitution principle. This basically states that instances of a subclass should be usable anywhere that instances of the superclass can be used. A programming language cannot guarantee that, and developers sometimes get sloppy when creating subclasses. So, Kotlin forces developers to make a conscious decision to allow subclasses, in hopes that it helps those developers take appropriate defensive programming steps to ensure that subclasses behave as expected.

Overriding Functions

As you might expect, a subclass can call functions that it inherits from its superclass:

open class Base(val tag: String) {
  fun gimmeTheTag() = tag
}

class Foo(var count: Int) : Base("Foo Example") {
  fun something() {
    count += 1
    println("${gimmeTheTag()}: something() was called $count times")
  }
}

fun main() {
  val foo = Foo(0)

  foo.something()
  foo.something()
  foo.something()

  println("the final count was ${foo.count}")
}

And, as you might expect, a subclass can override functions of its superclasses… at least, if those too are marked as open. By default, a Kotlin function is not open and cannot be overridden. Not only must the superclass function be marked as open, but the overriding function must be marked with override:

open class Base(val tag: String) {
  open fun gimmeTheTag() = tag
}

class Foo(var count: Int) : Base("Foo Example") {
  override fun gimmeTheTag() = "Not the Original Tag"

  fun something() {
    count += 1
    println("${gimmeTheTag()}: something() was called $count times")
  }
}

fun main() {
  val foo = Foo(0)

  foo.something()
  foo.something()
  foo.something()

  println("the final count was ${foo.count}")
}

Here, gimmeTheTag() is an open function in Base, so Foo can override it with its own implementation. The string interpolation will use the overridden function:

Not the Original Tag: something() was called 1 times
Not the Original Tag: something() was called 2 times
Not the Original Tag: something() was called 3 times
the final count was 3

By default, open is transitive. Once a function is marked as open, it is open for all subclasses down the hierarchy, regardless of whether intervening classes override the function or not:

open class Base(val tag: String) {
  open fun gimmeTheTag() = tag
}

open class Intermezzo(val thing: String) : Base(thing)

class Foo(var count: Int) : Intermezzo("Foo Example") {
  override fun gimmeTheTag() = "Not the Original Tag"

  fun something() {
    count += 1
    println("${gimmeTheTag()}: something() was called $count times")
  }
}

fun main() {
  val foo = Foo(0)

  foo.something()
  foo.something()
  foo.something()

  println("the final count was ${foo.count}")
}

Here, even though Foo now extends Intermezzo, it can still override gimmeTheTag(). It could do so even if Intermezzo itself overrides gimmeTheTag()… unless Intermezzo marks its version of that function as final:

open class Base(val tag: String) {
  open fun gimmeTheTag() = tag
}

open class Intermezzo(val thing: String): Base(thing) {
  final override fun gimmeTheTag() = "-$thing-"
}

class Foo(var count: Int) : Intermezzo("Foo Example") {
  override fun gimmeTheTag() = "Not the Original Tag"

  fun something() {
    count += 1
    println("${gimmeTheTag()}: something() was called $count times")
  }
}

val foo = Foo(0)

foo.something()
foo.something()
foo.something()

This results in an error:

error: 'gimmeTheTag' in 'Intermezzo' is final and cannot be overridden
  override fun gimmeTheTag() = "Not the Original Tag"
  ^

Once again, the objective is to force everybody to think about the ramifications of allowing functions to be overridden and the ramifications of overriding those functions.

Overriding Properties

It is also possible for a class to declare a property as open, allowing subclasses to override it.

Mostly this is needed for properties declared as constructor parameters. For example, let’s go back to this script:

open class Base(val tag: String) {
  open fun gimmeTheTag() = tag
}

open class Intermezzo(val thing: String) : Base(thing)

class Foo(var count: Int) : Intermezzo("Foo Example") {
  override fun gimmeTheTag() = "Not the Original Tag"

  fun something() {
    count += 1
    println("${gimmeTheTag()}: something() was called $count times")
  }
}

fun main() {
  val foo = Foo(0)

  foo.something()
  foo.something()
  foo.something()

  println("the final count was ${foo.count}")
}

Note that the constructor parameter for Base is called tag, while the constructor parameter for Intermezzo is called thing. They happen to be the same, um, thing, since Intermezzo passes its thing property to the call to the Base constructor.

Suppose, though, that we wanted to name these the same, such as having them both be tag:

open class Base(val tag: String) {
  open fun gimmeTheTag() = tag
}

open class Intermezzo(val tag: String): Base(tag)

class Foo(var count: Int) : Intermezzo("Foo Example") {
  override fun gimmeTheTag() = "Not the Original Tag"

  fun something() {
    count += 1
    println("${gimmeTheTag()}: something() was called $count times")
  }
}

val foo = Foo(0)

foo.something()
foo.something()
foo.something()

This results in a compile error:

error: 'tag' hides member of supertype 'Base' and needs 'override' modifier
open class Intermezzo(val tag: String): Base(tag)
                          ^

This is because both are declared as properties, and by default you cannot re-declare a property by reusing the name.

One way to address this is to realize that we do not need the constructor parameter in Intermezzo to be a property. It could be a plain constructor parameter:

open class Base(val tag: String) {
  open fun gimmeTheTag() = tag
}

open class Intermezzo(tag: String): Base(tag)

class Foo(var count: Int) : Intermezzo("Foo Example") {
  override fun gimmeTheTag() = "Not the Original Tag"

  fun something() {
    count += 1
    println("${gimmeTheTag()}: something() was called $count times")
  }
}

val foo = Foo(0)

foo.something()
foo.something()
foo.something()

This works, even though we reuse the tag name in Intermezzo, because a plain tag constructor parameter does not conflict with an inherited val property, even one that itself is declared as a constructor parameter.

What the compile error suggests, though, is that we can make tag in Base be open, then override it in Intermezzo:

open class Base(open val tag: String) {
  open fun gimmeTheTag() = tag
}

open class Intermezzo(override val tag: String) : Base(tag)

class Foo(var count: Int) : Intermezzo("Foo Example") {
  override fun gimmeTheTag() = "Not the Original Tag"

  fun something() {
    count += 1
    println("${gimmeTheTag()}: something() was called $count times")
  }
}

fun main() {
  val foo = Foo(0)

  foo.something()
  foo.something()
  foo.something()

  println("the final count was ${foo.count}")
}

In other words, you can override properties and change their behavior — we will examine this more much later in the book.

Chaining to Superclass Functions

Like Java, Kotlin uses a super. prefix to allow code in a subclass to specifically call implementations in a superclass. Typically you do this in a function that you overrode, to execute the superclass implementation as part of the overridden function’s implementation.

open class Base(val tag: String) {
  open fun gimmeTheTag() = tag
}

class Foo(var count: Int) : Base("Foo Example") {
  override fun gimmeTheTag() = super.gimmeTheTag() + "-Extended"

  fun something() {
    count += 1
    println("${gimmeTheTag()}: something() was called $count times")
  }
}

fun main() {
  val foo = Foo(0)

  foo.something()
  foo.something()
  foo.something()

  println("the final count was ${foo.count}")
}

Here, the Foo implementation of gimmeTheTag() calls the superclass implementation and appends -Extended to it. As a result, we get the combined results of both the Base and the Foo implementations of gimmeTheTag():

Foo Example-Extended: something() was called 1 times
Foo Example-Extended: something() was called 2 times
Foo Example-Extended: something() was called 3 times
the final count was 3

Inheritance Tests

Sometimes, we need to know whether a reference to some supertype is an instance of some subtype.

In Java, usually we do this via instanceof. So if we have a class hierarchy like this:

class Animal {
  // stuff
}

class Frog extends Animal {
  // froggy stuff
}

class Axolotl extends Animal {
  // axolotly stuff
}

…we can use instanceof to see if some Animal is really a Frog:

void something(Animal critter) {
  if (critter instanceof Frog) {
    // do froggy things
  }
}

In Ruby, kind_of? or is_a? are the typical ways of accomplishing the same thing:

def something(critter)
  if critter.is_a? Frog
    # do froggy things
  end
end

Kotlin has syntax similar to Java, with a shorter keyword: is. So, we can do this:

open class Animal

class Frog : Animal()

class Axolotl : Animal()

fun main() {
  val critter: Animal = Frog()

  if (critter is Frog) println("Ribbit!") else println("Ummm... whatever noise an axolotl makes!")
}

Here, critter is an Animal, but we use is to determine whether it is really pointing to an instance of Frog or not.

Inheritance and when

In Kotlin, we can use is not only as part of an expression, but we can use it as a test for a when clause. We can rewrite the previous example using a when:

open class Animal

class Frog : Animal()

class Axolotl : Animal()

fun main() {
  val critter: Animal = Frog()

  when (critter) {
    is Frog -> println("Ribbit!")
    else -> println("Ummm... whatever noise an axolotl makes!")
  }
}

This is helpful if you have a series of subtypes to check and handle separately, though you may be better served by overriding functions and putting the logic in the classes themselves (e.g., have a makeSound() function that all Animal classes implement, so you can just call makeSound() on any Animal and get the desired result).

Casting

Java programmers have a long history with casting objects. This turns a reference to a supertype into a subtype. In Java, we use the subtype name in parentheses to perform the cast. So, if we have the Animal, Frog, and Axolotl types as shown above, we might have a method that looks like this:

void something(Animal critter) {
  if (critter instanceof Frog) {
    Frog frog = (Frog)critter;

    // do something with the frog, preferably not involving boiling water
  }
  else {
    Axolotl axolotl = (Axolotl)critter;

    // do... oh, I don't know, I'm sure you can think of something
  }
}

In Kotlin, casting gets a bit more interesting, because the compiler tries to help reduce the number of casts that you need. In fact, well-written Kotlin code rarely involves casting.

Manual

That being said, you can certainly cast types. In Kotlin, that is performed by using the as keyword:

val critter: Animal = Frog()
val kermit = critter as Frog

Here, kermit will be of type Frog, as we manually cast the Animal to Frog via as Frog.

Smart Casts

Let’s go back to the Java snippet for a moment:

void something(Animal critter) {
  if (critter instanceof Frog) {
    Frog frog = (Frog)critter;

    // do something with the frog, preferably not involving boiling water
  }
  else {
    Axolotl axolotl = (Axolotl)critter;

    // do... oh, I don't know, I'm sure you can think of something
  }
}

In the if block, we cast critter to be a Frog. We know this is safe, because we checked the type validity with instanceof in the if test.

So, if we know that critter is really a Frog… why doesn’t Java?

Presumably, the Kotlin developers asked themselves that sort of question, though possibly not involving frogs. Inside of if and when blocks, if we know that a variable is really of some subtype, the compiler lets us skip the casts and allows us to refer to the variable as the subtype, even though it was declared as a supertype.

For example, let’s give our creatures some functions, then call those functions:

open class Animal

class Frog : Animal() {
  fun hop() = println("Hop!")
}

class Axolotl : Animal() {
  fun swim() = println("Swish!")
}

fun main() {
  val critter: Animal = Frog()

  when (critter) {
    is Frog -> critter.hop()
    is Axolotl -> critter.swim()
  }
}

In a Java-style world, this would not compile. critter is an Animal, and Animal has neither hop() nor swim(). However, Kotlin realizes that the is Frog code will only be executed if critter is a Frog, so it allows us to call hop() on critter, as if critter were declared as a Frog instead of as an Animal. Similarly, we can call swim() on critter if it is Axolotl.

However, this only works when the compiler is certain of the type. As a result, this subtly-different edition of that code fails with a compile error:

open class Animal

class Frog : Animal() {
  fun hop() = println("Hop!")
}

class Axolotl : Animal() {
  fun swim() = println("Swish!")
}

val critter: Animal = Frog()

when (critter) {
  is Frog -> critter.hop()
  else -> critter.swim()
}

Here, we replace is Axolotl with else. The Kotlin compiler knows that critter is not a Frog, as otherwise we would not be executing the else. However, critter might not be an Axolotl, as it could be an instance of Animal. As a result, Kotlin cannot assume that it is safe to call swim() on critter, as while an Axolotl can swim(), an Animal cannot. So, we get a compile error:

error: unresolved reference: swim
  else -> critter.swim()

To get it to compile, we need to manually cast the critter to be an Axolotl:

open class Animal

class Frog : Animal() {
  fun hop() = println("Hop!")
}

class Axolotl : Animal() {
  fun swim() = println("Swish!")
}

fun main() {
  val critter: Animal = Frog()

  when (critter) {
    is Frog -> critter.hop()
    else -> (critter as Axolotl).swim()
  }
}

But, by doing this, we might crash, if critter is neither a Frog nor an Axolotl. So, while this compiles:

open class Animal

class Frog : Animal() {
  fun hop() = println("Hop!")
}

class Axolotl : Animal() {
  fun swim() = println("Swish!")
}

val critter: Animal = Animal()

when (critter) {
  is Frog -> critter.hop()
  else -> (critter as Axolotl).swim()
}

…it crashes at runtime, such as this error message from Kotlin/JVM:

java.lang.ClassCastException: Test$Animal cannot be cast to Test$Axolotl
  at Test.<init>(Unknown Source)

Kotlin’s “smart casts” — as this automatic casting is called — not only saves you typing, but it also helps you avoid improper manual casts. In general, if you find yourself using as in Kotlin, you should be asking yourself: if the Kotlin compiler does not know that this object is of this subtype… how do I know that this object will always be of this subtype?


Prev Table of Contents Next

This book is licensed under the Creative Commons Attribution-ShareAlike 4.0 International license.