Kotlin !is Keyword
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.
Basic Definitions
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.
Basic !is Check
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.
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.
!is with Smart Cast
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.
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.
!is in When Expressions
The !is operator works well in when expressions to
handle multiple type cases. It helps create exhaustive type checking logic.
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.
!is with Null Checks
The !is operator can be combined with null checks to create more
complex conditions. This is particularly useful when dealing with nullable types.
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.
!is in Generic Functions
The !is operator can be used in generic functions to perform type
checks on generic parameters. This requires reified type parameters.
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.
!is with Collections
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.
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.
!is in Class Hierarchies
The !is operator is particularly useful when working with class
hierarchies. It helps determine when an object is not of a specific subclass.
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.
Best Practices for !is Operator
- Combine with smart casts: Leverage Kotlin's smart casting
after
!ischecks for cleaner code. - Use in when expressions:
!isworks well inwhenexpressions for exhaustive type checking. - Consider nullability: Remember to handle null cases when
using
!iswith nullable types. - Prefer positive checks: Sometimes
iswith an else branch is clearer than!is. - Use with reified generics: For generic functions, use
reified type parameters with
!is.
Source
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.
Author
List all Kotlin tutorials.