Kotlin is Keyword
last modified April 19, 2025
Kotlin's type checking system provides powerful ways to verify object types at
runtime. The is
keyword is central to type checking operations. This
tutorial explores the is
keyword in depth with practical examples.
Basic Definitions
The is
keyword in Kotlin checks whether an object is of a specific
type. It returns true if the object matches the type, false otherwise. When used
with smart casts, it enables automatic type conversion within scope.
Basic Type Checking
The simplest use of is
checks if an object is of a certain type.
This is useful when working with generic types like Any
.
package com.zetcode fun main() { val obj: Any = "Hello Kotlin" if (obj is String) { println("It's a String") } else { println("It's not a String") } }
Here we check if obj
is a String. The is
operator
returns true, so the first branch executes. This is the most straightforward
use of type checking in Kotlin.
Smart Casting
Kotlin's smart casting automatically casts objects after type checks. Within the
scope where is
returns true, the object is treated as that type.
package com.zetcode fun printLength(obj: Any) { if (obj is String) { println(obj.length) // Smart cast to String } } fun main() { printLength("Kotlin") // Output: 6 printLength(123) // No output }
After checking obj is String
, Kotlin automatically treats obj as a
String inside the if block. We can directly call String methods like length
without explicit casting.
Negative Check with !is
The !is
operator is the negation of is
. It checks if
an object is not of a certain type. Smart casting works with negative checks too.
package com.zetcode fun processValue(value: Any) { if (value !is String) { println("Not a string: $value") } else { println(value.uppercase()) } } fun main() { processValue(42) // Output: Not a string: 42 processValue("hello") // Output: HELLO }
The !is
operator checks if value is not a String. In the else
branch, value is smart cast to String, allowing us to call uppercase().
When Expressions with is
The is
keyword works well with when
expressions. This
allows for clean pattern matching against multiple possible types.
package com.zetcode fun describe(obj: Any): String = when (obj) { is String -> "String with length ${obj.length}" is Int -> "Integer with value $obj" is Double -> "Double with value $obj" else -> "Unknown type" } fun main() { println(describe("Kotlin")) // Output: String with length 6 println(describe(42)) // Output: Integer with value 42 println(describe(3.14)) // Output: Double with value 3.14 }
The when
expression checks the type of obj using is
.
Each branch automatically smart casts to the checked type, allowing type-specific
operations. This is a clean alternative to if-else chains.
Checking Generic Types
With reified type parameters, you can check generic types at runtime. This
extends is
functionality to generic type checking.
package com.zetcode inline fun <reified T> checkType(obj: Any) { if (obj is T) { println("Object is of type ${T::class.simpleName}") } else { println("Object is not of type ${T::class.simpleName}") } } fun main() { checkType<String>("Hello") // Output: Object is of type String checkType<Int>("World") // Output: Object is not of type Int }
The reified type parameter preserves type information at runtime. This allows
is
to check against generic type T. Without reified, this check
wouldn't be possible due to type erasure.
Checking Sealed Classes
The is
keyword is particularly useful with sealed classes. It allows
exhaustive checking of all possible subtypes in a type-safe way.
package com.zetcode sealed class Result class Success(val data: String) : Result() class Error(val message: String) : Result() fun handleResult(result: Result) { when (result) { is Success -> println("Success: ${result.data}") is Error -> println("Error: ${result.message}") } } fun main() { handleResult(Success("Data loaded")) // Output: Success: Data loaded handleResult(Error("Network error")) // Output: Error: Network error }
The sealed class ensures all possible subtypes are known. The when
expression with is
checks provides exhaustive handling. Each branch
smart casts to the specific subtype, allowing access to subtype properties.
Checking Nullable Types
The is
operator handles nullable types gracefully. It can check both
the type and null status of an object in one operation.
package com.zetcode fun checkNullable(value: Any?) { if (value is String?) { println("It's a nullable String") } if (value is String) { println("It's a non-null String: $value") } else if (value == null) { println("It's null") } } fun main() { checkNullable("Hello") // Output: It's a nullable String + non-null String checkNullable(null) // Output: It's a nullable String + It's null }
The first check with String?
matches both String and null. The
second check with String
only matches non-null Strings. This shows
how is
can distinguish between nullable and non-null types.
Best Practices for Type Checking
- Prefer smart casts: Use
is
checks to enable automatic type conversion within scope. - Combine with when: Use
is
in when expressions for clean pattern matching. - Handle nulls properly: Consider nullable types in your
is
checks when working with nullable references. - Use sealed classes: Combine
is
with sealed classes for exhaustive type checking. - Consider performance: While
is
checks are generally fast, avoid unnecessary checks in performance-critical code.
Source
Kotlin Type Checks and Casts Documentation
This tutorial covered Kotlin's is
keyword in depth, showing its use
for type checking and enabling smart casts. We explored various scenarios
including sealed classes, generics, and nullable types. Proper use of type
checking can make your code more robust and type-safe.
Author
List all Kotlin tutorials.