Type checks and casts
is and !is operators
is operator or its negated form
!is to perform a runtime check that identifies whether an object conforms to a given type:
In most cases, you don't need to use explicit cast operators in Kotlin because the compiler tracks the
is-checks and explicit casts for immutable values and inserts (safe) casts automatically when necessary:
The compiler is smart enough to know that a cast is safe if a negative check leads to a return:
or if it is on the right-hand side of
|| and the proper check (regular or negative) is on the left-hand side:
Note that smart casts work only when the compiler can guarantee that the variable won't change between the check and the usage. More specifically, smart casts can be used under the following conditions:
vallocal variables - always, with the exception of local delegated properties.
valproperties - if the property is private or internal or if the check is performed in the same module where the property is declared. Smart casts cannot be used on open properties or properties that have custom getters.
varlocal variables - if the variable is not modified between the check and the usage, is not captured in a lambda that modifies it, and is not a local delegated property.
varproperties - never, because the variable can be modified at any time by other code.
"Unsafe" cast operator
Usually, the cast operator throws an exception if the cast isn't possible. And so, it's called unsafe. The unsafe cast in Kotlin is done by the infix operator
null cannot be cast to
String, as this type is not nullable. If
y is null, the code above throws an exception. To make code like this correct for null values, use the nullable type on the right-hand side of the cast:
"Safe" (nullable) cast operator
To avoid exceptions, use the safe cast operator
as?, which returns
null on failure.
Note that despite the fact that the right-hand side of
as? is a non-null type
String, the result of the cast is nullable.
Type erasure and generic type checks
Kotlin ensures type safety for operations involving generics at compile time, while, at runtime, instances of generic types don't hold information about their actual type arguments. For example,
List<Foo> is erased to just
List<*>. In general, there is no way to check whether an instance belongs to a generic type with certain type arguments at runtime.
Because of that, the compiler prohibits
is-checks that cannot be performed at runtime due to type erasure, such as
ints is List<Int> or
list is T (type parameter). You can, however, check an instance against a star-projected type:
Similarly, when you already have the type arguments of an instance checked statically (at compile time), you can make an
is-check or a cast that involves the non-generic part of the type. Note that angle brackets are omitted in this case:
The same syntax but with the type arguments omitted can be used for casts that do not take type arguments into account:
list as ArrayList.
Inline functions with reified type parameters have their actual type arguments inlined at each call site. This enables
arg is T checks for the type parameters, but if
arg is an instance of a generic type itself, its type arguments are still erased.
As established above, type erasure makes checking the actual type arguments of a generic type instance impossible at runtime. Additionally, generic types in the code might not be connected to each other closely enough for the compiler to ensure type safety.
Even so, sometimes we have high-level program logic that implies type safety instead. For example:
A warning appears for the cast in the last line. The compiler can't fully check it at runtime and provides no guarantee that the values in the map are
To avoid unchecked casts, you can redesign the program structure. In the example above, you could use the
DictionaryWriter<T> interfaces with type-safe implementations for different types. You can introduce reasonable abstractions to move unchecked casts from the call site to the implementation details. Proper use of generic variance can also help.
For generic functions, using reified type parameters makes casts like
arg as T checked, unless
arg's type has its own type arguments that are erased.
An unchecked cast warning can be suppressed by annotating the statement or the declaration where it occurs with