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
ischecks to enable automatic type conversion within scope. - Combine with when: Use
isin when expressions for clean pattern matching. - Handle nulls properly: Consider nullable types in your
ischecks when working with nullable references. - Use sealed classes: Combine
iswith sealed classes for exhaustive type checking. - Consider performance: While
ischecks 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.