Kotlin !is keyword tutorial shows how to perform type checking in Kotlin. Learn about negative type checks and smart casts with examples.
last modified April 19, 2025
Kotlin’s type checking system provides powerful operators to verify types at runtime. The !is keyword is used for negative type checks. This tutorial explores the !is keyword in depth with practical examples.
The !is keyword in Kotlin is the negation of the is operator. It checks if an object is NOT an instance of a specific type. Like is, it enables smart casts in the corresponding code block.
The simplest use of !is is to verify that an object is not of a certain type. This is useful when you want to handle different types differently.
BasicNotIs.kt
package com.zetcode
fun main() {
val obj: Any = 42
if (obj !is String) {
println("Object is not a String")
}
}
Here we check if obj is not a String. Since it’s an Int, the condition is true and the message is printed. This is the most basic use of !is.
After a !is check, Kotlin smart casts the object to the negated type in the corresponding block. This allows you to safely use methods of other types.
SmartCastNotIs.kt
package com.zetcode
fun printIfNotString(obj: Any) { if (obj !is String) { println(“Value is ${obj} and type is ${obj.javaClass.simpleName}”) } }
fun main() {
printIfNotString(123) // Output: Value is 123 and type is Integer
printIfNotString(true) // Output: Value is true and type is Boolean
}
In this example, after the !is String check, Kotlin knows obj isn’t a String. We can then safely access properties of the actual type, like javaClass.
The !is operator works well in when expressions to handle multiple type cases. It helps create exhaustive type checking logic.
WhenNotIs.kt
package com.zetcode
fun describeType(obj: Any): String { return when (obj) { is String -> “String of length ${obj.length}” !is Number -> “Not a number” else -> “Number with value ${obj.toDouble()}” } }
fun main() {
println(describeType("Hello")) // String of length 5
println(describeType(true)) // Not a number
println(describeType(42)) // Number with value 42.0
}
This example shows how !is can be used in a when expression. The !is Number branch catches all non-number types after the String check.
The !is operator can be combined with null checks to create more complex conditions. This is particularly useful when dealing with nullable types.
NullCheckNotIs.kt
package com.zetcode
fun processValue(value: Any?) { if (value !is String? || value == null) { println(“Not a string or null”) } else { println(“String length is ${value.length}”) } }
fun main() {
processValue(null) // Not a string or null
processValue(123) // Not a string or null
processValue("Kotlin") // String length is 6
}
Here we combine !is with a null check to handle both non-string types and null values. The smart cast ensures we can safely access String properties in the else branch.
The !is operator can be used in generic functions to perform type checks on generic parameters. This requires reified type parameters.
GenericNotIs.kt
package com.zetcode
inline fun <reified T> isNotType(obj: Any): Boolean { return obj !is T }
fun main() {
println(isNotType<String>("Hello")) // false
println(isNotType<Int>("World")) // true
println(isNotType<List<*>>(emptyList<String>())) // false
}
This generic function uses !is with a reified type parameter to check if an object is not of type T. The reified parameter preserves the type information at runtime.
The !is operator can be used to check collection types or to filter collections based on type. This is useful when working with heterogeneous collections.
CollectionNotIs.kt
package com.zetcode
fun main() {
val mixedList: List<Any> = listOf(1, "two", 3.0, "four", 5)
val nonStrings = mixedList.filter { it !is String }
println("Non-string elements: $nonStrings")
if (mixedList !is List<String>) {
println("List contains non-string elements")
}
}
Here we use !is to filter out String elements from a mixed list. We also check that the entire list isn’t a List<String>. Both uses demonstrate !is with collections.
The !is operator is particularly useful when working with class hierarchies. It helps determine when an object is not of a specific subclass.
HierarchyNotIs.kt
package com.zetcode
open class Animal class Dog : Animal() { fun bark() = “Woof!” } class Cat : Animal() { fun meow() = “Meow!” }
fun makeSound(animal: Animal) { if (animal !is Dog) { println(“Not a dog”) if (animal is Cat) { println(animal.meow()) } } else { println(animal.bark()) } }
fun main() {
makeSound(Dog()) // Woof!
makeSound(Cat()) // Not a dog \n Meow!
}
This example shows !is in a class hierarchy. We first check if the animal is not a Dog, then handle other cases. The smart cast allows calling Cat-specific methods in the appropriate block.
Combine with smart casts: Leverage Kotlin’s smart casting after !is checks for cleaner code. Use in when expressions: !is works well in when expressions for exhaustive type checking. Consider nullability: Remember to handle null cases when using !is with nullable types. Prefer positive checks: Sometimes is with an else branch is clearer than !is. Use with reified generics: For generic functions, use reified type parameters with !is.
Kotlin Type Checks and Casts Documentation
This tutorial covered Kotlin’s !is keyword in depth, showing its use in various scenarios including smart casts, when expressions, and generics. The !is operator is a powerful tool for type-safe Kotlin code when used appropriately.
My name is Jan Bodnar, and I am a passionate programmer with many years of programming experience. I have been writing programming articles since 2007. So far, I have written over 1400 articles and 8 e-books. I have over eight years of experience in teaching programming.
List all Kotlin tutorials.