# Downcasting

Downcasting discovers the specific type of a previously-upcast object.

Upcasts are always safe because the base class cannot have a bigger interface than the derived class. Every base-class member is guaranteed to exist and is therefore safe to call. Although object-oriented programming is primarily focused on upcasting, there are situations where downcasting can be a useful and expedient approach.

Downcasting happens at runtime, and is also called run-time type identification (RTTI).

Consider a class hierarchy where the base type has a narrower interface than the derived types. If you upcast an object to the base type, the compiler no longer knows the specific type. In particular, it cannot know what extended functions are safe to call:

// DownCasting/NarrowingUpcast.kt
package downcasting

interface Base {
  fun f()
}

class Derived1 : Base {
  override fun f() {}
  fun g() {}
}

class Derived2 : Base {
  override fun f() {}
  fun h() {}
}

fun main() {
  val b1: Base = Derived1() // Upcast
  b1.f()    // Part of Base
  // b1.g() // Not part of Base
  val b2: Base = Derived2() // Upcast
  b2.f()    // Part of Base
  // b2.h() // Not part of Base
}

To solve this problem, there must be some way to guarantee that a downcast is correct, so you don’t accidentally cast to the wrong type and call a non-existent member.

# Smart Casts

Smart casts in Kotlin are automatic downcasts. The is keyword checks whether an object is a particular type. Any code within the scope of that check assumes that it is that type:

// DownCasting/IsKeyword.kt
import downcasting.*

fun main() {
  val b1: Base = Derived1() // Upcast
  if(b1 is Derived1)
    b1.g() // Within scope of "is" check
  val b2: Base = Derived2() // Upcast
  if(b2 is Derived2)
    b2.h() // Within scope of "is" check
}

If b1 is of type Derived1, you can call g(). If b2 is of type Derived2, you can call h().

Smart casts are especially useful inside when expressions that use is to search for the type of the when argument. Note that, in main(), each specific type is first upcast to a Creature, then passed to what():

// DownCasting/Creature.kt
package downcasting
import atomictest.eq

interface Creature

class Human : Creature {
  fun greeting() = "I'm Human"
}

class Dog : Creature {
  fun bark() = "Yip!"
}

class Alien : Creature {
  fun mobility() = "Three legs"
}

fun what(c: Creature): String =
  when (c) {
    is Human -> c.greeting()
    is Dog -> c.bark()
    is Alien -> c.mobility()
    else -> "Something else"
  }

fun main() {
  val c: Creature = Human()
  what(c) eq "I'm Human"
  what(Dog()) eq "Yip!"
  what(Alien()) eq "Three legs"
  class Who : Creature
  what(Who()) eq "Something else"
}

In main(), upcasting happens when assigning a Human to Creature, passing a Dog to what(), passing an Alien to what(), and passing a Who to what().

Class hierarchies are traditionally drawn with the base class at the top and derived classes fanning down below it. what() takes a previously-upcast Creature and discovers its exact type, thus casting that Creature object down the inheritance hierarchy, from the more-general base class to a more-specific derived class.

A when expression that produces a value requires an else branch to capture all remaining possibilities. In main(), the else branch is tested using an instance of the local class Who.

Each branch of the when uses c as if it is the type we checked for: calling greeting() if c is Human, bark() if it’s a Dog and mobility() if it’s an Alien.

# The Modifiable Reference

Automatic downcasts are subject to a special constraint. If the base-class reference to the object is modifiable (a var), then there’s a possibility that this reference could be assigned to a different object between the instant that the type is detected and the instant when you call specific functions on the downcast object. That is, the specific type of the object might change between type detection and use.

In the following, c is the argument to when, and Kotlin insists that this argument be immutable so that it cannot change between the is expression and the call made after the ->:

// DownCasting/MutableSmartCast.kt
package downcasting

class SmartCast1(val c: Creature) {
  fun contact() {
    when (c) {
      is Human -> c.greeting()
      is Dog -> c.bark()
      is Alien -> c.mobility()
    }
  }
}

class SmartCast2(var c: Creature) {
  fun contact() {
    when (val c = c) {           // [1]
      is Human -> c.greeting()   // [2]
      is Dog -> c.bark()
      is Alien -> c.mobility()
    }
  }
}

The c constructor argument is a val in SmartCast1 and a var in SmartCast2. In both cases c is passed into the when expression, which uses a series of smart casts.

In [1], the expression val c = c looks odd, and only used here for convenience—we don’t recommend “shadowing” identifier names in normal code. val c creates a new local identifier c that captures the value of the property c. However, the property c is a var while the local (shadowed) c is a val. Try removing the val c =. This means that the c will now be the property, which is a var. This produces an error message for line [2]:

  • Smart cast to ‘Human’ is impossible, because ‘c’ is a mutable property that could have been changed by this time

is Dog and is Alien produce similar messages. This is not limited to while expressions; there are other situations that can produce the same error message.

The change described in the error message typically happens through concurrency, when multiple independent tasks have the opportunity to change c at unpredictable times. (Concurrency is an advanced topic that we do not cover in this book).

Kotlin forces us to ensure that c will not change from the time that the is check is performed and the time that c is used as the downcast type. SmartCast1 does this by making the c property a val, and SmartCast2 does it by introducing the local val c.

Similarly, complex expressions cannot be smart-cast because the expression might be re-evaluated. Properties that are open for inheritance can’t be smart-cast because their value might be overridden in subclasses, so there’s no guarantee the value will be the same on the next access.

# The as Keyword

The as keyword forcefully casts a general type to a specific type:

// DownCasting/Unsafe.kt
package downcasting
import atomictest.*

fun dogBarkUnsafe(c: Creature) =
  (c as Dog).bark()

fun dogBarkUnsafe2(c: Creature): String {
  c as Dog
  c.bark()
  return c.bark() + c.bark()
}

fun main() {
  dogBarkUnsafe(Dog()) eq "Yip!"
  dogBarkUnsafe2(Dog()) eq "Yip!Yip!"
  (capture {
    dogBarkUnsafe(Human())
  }) contains listOf("ClassCastException")
}

dogBarkUnsafe2() shows a second form of as: if you say c as Dog, then c is treated as a Dog throughout the rest of the scope.

A failing as cast throws a ClassCastException. A plain as is called an unsafe cast.

When a safe cast as? fails, it doesn’t throw an exception, but instead returns null. You must then do something reasonable with that null to prevent a later NullPointerException. The Elvis operator (described in [Safe Calls & the Elvis Operator](javascript:void(0))) is usually the most straightforward approach:

// DownCasting/Safe.kt
package downcasting
import atomictest.eq

fun dogBarkSafe(c: Creature) =
  (c as? Dog)?.bark() ?: "Not a Dog"

fun main() {
  dogBarkSafe(Dog()) eq "Yip!"
  dogBarkSafe(Human()) eq "Not a Dog"
}

If c is not a Dog, as? produces a null. Thus, (c as? Dog) is a nullable expression and we must use the safe call operator ?. to call bark(). If as? produces a null, then the whole expression (c as? Dog)?.bark() will also produce a null, which the Elvis operator handles by producing "Not a Dog".

# Discovering Types in Lists

When used in a predicate, is finds objects of a given type within a List, or any iterable (something you can iterate through):

// DownCasting/FindType.kt
package downcasting
import atomictest.eq

val group: List<Creature> = listOf(
  Human(), Human(), Dog(), Alien(), Dog()
)

fun main() {
  val dog = group
    .find { it is Dog } as Dog?    // [1]
  dog?.bark() eq "Yip!"            // [2]
}

Because group contains Creatures, find() returns a Creature. We want to treat it as a Dog, so we explicitly cast it at the end of line [1]. There might be zero Dogs in group, in which case find() returns a null so we must cast the result to a nullable Dog?. Because dog is nullable, we use the safe call operator in line [2].

You can usually avoid the code in line [1] by using filterIsInstance(), which produces all elements of a specific type:

// DownCasting/FilterIsInstance.kt
import downcasting.*
import atomictest.eq

fun main() {
  val humans1: List<Creature> =
    group.filter { it is Human }
  humans1.size eq 2
  val humans2: List<Human> =
    group.filterIsInstance<Human>()
  humans2 eq humans1
}

filterIsInstance() is a more readable way to produce the same result as filter(). However, the result types are different: while filter() returns a List of Creature (even though all the resulting elements are Human), filterIsInstance() returns a list of the target type Human. We’ve also eliminated the nullability issues seen in FindType.kt.

Exercises and solutions can be found at www.AtomicKotlin.com.