skip to content

Show idiomatic ways to use `as?` to avoid ClassCastException, and explain how it interacts with smart casts and the Elvis operator.

level: middleimportance: should knowfreq 50%

answer

  1. as? + ?: = safe cast idiom
  2. after ?: return, value is smart-cast non-null
  3. prefer is for type branches (auto smart-cast)
  4. equals(): other as? T ?: return false
  5. use at Any/Map/Bundle/JSON boundaries

basics

~20 s

Use as? to try a cast and get null if it does not fit, then handle the null with the ?: operator to provide a default or return early. This avoids crashes from a bad cast.

solid answer

~40 s

The idiom is val typed = value as? Target ?: <fallback-or-return>. Because as? returns Target? (nullable), the Elvis operator ?: gives a default or an early exit, and after a non-null guard the variable is smart-cast to non-null Target. For type branching, prefer is, which smart-casts the value inside the branch with no operator needed: if (x is Target) { x.member }. Override equals is a classic use: other as? T ?: return false. Avoid bare as in these spots since a mismatch would throw ClassCastException. as? also helps when bridging Any from collections, JSON maps, or intents/bundles where the runtime type is uncertain.

code

kotlin · 10 lines
kotlin
data class Point(val x: Int, val y: Int) {
    override fun equals(other: Any?): Boolean {
        val p = other as? Point ?: return false
        return p.x == x && p.y == y
    }
    override fun hashCode() = 31 * x + y
}

fun config(map: Map<String, Any?>): Int =
    map["retries"] as? Int ?: 3   // default when missing or wrong type

go deeper

for a junior

Can write x as? T ?: default to avoid a crash.

for a middle

Explains the nullable result, smart cast after Elvis, and the equals() pattern.

for a senior

Chooses is vs as? deliberately and knows the smart-cast stability constraints (val vs var).

for a principal

Standardizes safe-cast patterns at untyped boundaries and reviews APIs to minimize Any-typed seams.

## The core idiom `as?` plus the **Elvis operator** `?:` is the canonical safe-cast pattern. Since `value as? Target` has type `Target?`, you immediately resolve the nullability: ```kotlin // fallback value val count = anyValue as? Int ?: 0 // early return fun handle(msg: Any) { val text = msg as? String ?: return println(text.uppercase()) // text is smart-cast to non-null String } ``` After `?: return` (or `?: throw`), the compiler knows the variable is non-null and **smart-casts** it, so you use it without `!!` or further casts. ## as? vs the is smart cast When you branch on type, prefer **`is`** — it performs a checked test and **smart-casts** the value to the narrowed type inside the block: ```kotlin fun area(shape: Any): Double = when (shape) { is Circle -> Math.PI * shape.r * shape.r // shape smart-cast to Circle is Square -> shape.side * shape.side // smart-cast to Square else -> 0.0 } ``` Use `as?` instead when you want a single nullable result rather than a branch, e.g., feeding into Elvis. ## Classic equals() override A textbook use is implementing `equals`, where the parameter is `Any?`: ```kotlin override fun equals(other: Any?): Boolean { val that = other as? Money ?: return false return that.cents == this.cents } ``` `as?` cleanly handles both "wrong type" and "null" in one expression — both yield `null`, and Elvis returns `false`. ## Bridging untyped boundaries `as?` shines at the edges where you receive `Any`/`Any?`: values out of an untyped `Map<String, Any?>`, Android `Bundle`/`Intent` extras, JSON parse trees, or `List<*>` elements. There the runtime type is genuinely uncertain, so a safe cast that degrades to `null` is safer than a throwing `as`. ## Smart-cast caveat Smart-casting after `as?` requires the compiler to prove the variable cannot change — works for `val` locals; mutable `var` properties accessed across calls or open `val` from another module may not smart-cast.

  • After `val s = x as? String ?: return`, why can you call `s.length` without a null check?
    Elvis returned on the null branch, so past that line the compiler smart-casts `s` to non-null `String`.
  • When should you use `is` instead of `as?`?
    When you branch on type — `is` checks and smart-casts inside the branch, avoiding an explicit cast and a nullable result.

saying these in an interview costs you the question

  • Using bare as in equals() (throws on wrong-type argument)
  • Adding !! after as? instead of handling null
  • Not knowing as? enables smart cast after an Elvis return
  • Claiming is requires an explicit cast inside the branch
  • Using as? everywhere even when is/when is clearer

context