Downcasting

1 minute read

Downcasting is to discover the specific type of a previously upcast object. Upcast is 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 identification (RTII).

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()
  //b1.g() // error, g() cannot access in b1

  val b2: Base = Derived2() // upcast
  b2.f()
  //b2.h() // error, h() cannot access in b2
}

As we can see, we cannot access b1.g() and b2.h() since both b1 and b2 are upcast to base.

Smart Cast

Smart Cast is automatic downcast. This is keyword checks whether an object is a particular type. Any code within the scope of that check assumes that it is that type:

fun main() {
  val b1: Base = Derived1()
  if (b1 is Derived1) // scope within the check
    b1.g()
}

The as keyword

The as keyword forcefully casts a general type to a specific type. 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.

interface Creature

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

class Human : Creature {
  fun talk() = "hello"
}

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


dogBarkSafely(Dog()) // woof!
dogBarkSafely(Human()) // Not a dog

Discovering Types in Lists

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

val dog = group.find { it is Dog } as Dog?
dog?.bark()

The code above can be written using filterIsInstance(), which produces all elements of a specific type.

val dogs : List<Human> = group.filterIsInstance<Human>()

filterIsInstance is more readable than using filter(). The difference between filter() and the filterIsInstance() is the return values. filter returns a List of Creature whereas filterIsInstance returns the target.

Tags:

Categories:

Updated: