liveBook · Manning

Unchecked cast

As explained in Kotlin in Action, 2E

An unchecked cast is a cast whose generic type compatibility cannot be verified by the compiler or runtime because the relevant type information is unavailable.

An unchecked cast converts a value to a generic type even though its type argument cannot be verified at runtime. Kotlin can confirm a value’s base type, such as List, but generic arguments such as Int are erased. The compiler therefore allows casts like `as List<Int>` or `as? List<Int>` with an unchecked-cast warning. A successful cast means only that the value has the requested base type; it does not prove that every element has the requested generic type. section 11.2.1

A cast that succeeds too early

`fun printSum(c: Collection<*>) { val ints = c as? List<Int> ?: throw IllegalArgumentException("List is expected"); println(ints.sum()) }` A list of integers prints its sum. A set of integers fails immediately because it is not a List. But a `List<String>` passes the cast: its runtime type is still List. The later call to `sum()` attempts to use strings as numbers and throws `ClassCastException`. Use `as?` to handle a wrong base type, not to validate generic arguments. section 11.2.1

Prefer checks that express only the information actually available. `is List<*>` checks for a list with unknown element type and avoids claiming that its elements are Int values. Kotlin can permit a check such as `is List<Int>` when the element type is already known statically—for example, when a parameter is `Collection<Int>`—because the check then distinguishes collection implementations rather than discovering erased element types. section 11.2.1

When an unchecked cast is unavoidable, make the unsafe boundary small:

  • Do not treat a cast from `FieldValidator<*>` to `FieldValidator<String>` as proof that the validator accepts strings; a mismatched validator fails only when validation receives the wrong value. section 11.3.6
  • Avoid explicit casts as a general way to use star-projected values. A star projection deliberately prevents supplying a value whose type is unknown. section 11.3.6
  • Encapsulate the cast behind a generic API that controls registration and lookup. Keep the warning, and any `@Suppress("UNCHECKED_CAST")`, inside that implementation rather than exposing it to callers. section 11.3.6
  • Make the enclosing API type-safe: register `FieldValidator<T>` with its matching `KClass<T>` and return the same `FieldValidator<T>` for a lookup. section 11.3.6

Localizing a validator-registry cast

A registry can store heterogeneous entries as `MutableMap<KClass<*>, FieldValidator<*>>`, then expose generic `registerValidator` and `get` operations. Its lookup performs the unchecked cast internally after the registry has controlled which validator was associated with each class. Callers receive a validator whose input type is checked by the compiler: requesting the String validator and passing `42` is rejected before runtime. section 11.3.6

Listing 11.5 Using a type cast with a generic type
fun printSum(c: Collection<*>) {
    val intList = c as? List<Int>                              #1
        ?: throw IllegalArgumentException("List is expected")
    println(intList.sum())
}

Inspect the compact `printSum` example to separate the base-type test performed by `as?` from the erased element-type claim that triggers the warning. section 11.2.1

Unchecked casts shift responsibility from Kotlin’s type system to the code that performs the cast. They are sometimes useful at a controlled interoperability or storage boundary, but they are error-prone when scattered through application logic. Prefer star projections for unknown generic arguments and a narrow, audited generic wrapper when heterogeneous values must be stored together. section 11.2.1 section 11.3.6

How Kotlin in Action, 2E covers it

How deep it goes
Explains erased generic checks through collection and validator failures, then develops an encapsulated generic registry pattern.
Examples
Demonstrates summing collections and retrieving String or Int field validators from a heterogeneous map.
What it assumes
Assumes Kotlin collection casts and generics, while introducing star projections as unknown generic arguments.
What you can do after
Evaluate/understand when a generic cast can succeed yet later throw ClassCastException, and localize necessary unsafe casts.
How it connects
Connects unchecked casts with type erasure, star projections, safe casts, generic type checks, and variance.

Also explained in

Compare how 2 books explain unchecked cast →

Related concepts

This explainer was generated by Manning's knowledge graph from the text of Kotlin in Action, 2E. The section links point to the parts of the book it draws on.