Blog / November 23, 2017 / 5 mins read / By Suneet Agrawal

TypeCheck (‘is’) and Cast (‘as’) in Kotlin

Type check is a way of checking the type(DataType) or Class of a particular instance or variable while runtime to separate the flow for different objects. In few languages, it’s also denoted as Run Time Type Identification (RTTI).

Let’s consider an example where we have an Interface called Shape having an abstract method as calculateArea.

interface Shape {
     fun calculateArea(): Float
}

We have three different classes implementing the same interface and implementing their own area calculating method according to their shapes.

class Circle : Shape {
    var radius: Float = 10.0f
    override fun calculateArea(): Float {
       return (22 * radius * radius) / 7
    }
}

class Square : Shape {
    var sideLength: Float = 10.0f
    override fun calculateArea(): Float {
       return sideLength * sideLength
    }
}

class Rectangle : Shape {
    var length: Float = 10.0f
    var breadth: Float = 5.0f
    override fun calculateArea(): Float {
       return length * breadth
    }
}

Now let’s create an object with reference to the Shape interface but implementation of different classes based on some condition.

var shapeObject: Shape
if (/* Some Condition*/) {
    shapeObject = Circle()
} else if (/* Some Other Condition*/) {
    shapeObject = Square()
} else {
    shapeObject = Rectangle()
}

Now if we want to tweak the properties of the variable shapeObject (radius in case of Circle, sideLength in case of Square and length and breadth in case of Rectangle), you can’t simply use them directly as the reference to that object is Shape interface.

shapeObject.radius = 10.0f //compile time error

Before accessing the properties of shapeObject one must ensure the type of variable shapeObject.

‘is’ and ‘!is’ Operators

is operator checks the type of variable and returns boolean as true if it matches the type.

if (shapeObject is Circle) {
    print("it’s a Circle")
} else if (shapeObject is Square) {
    print("it’s a Square")
} else if (shapeObject is Rectangle) {
    print("it’s a Rectangle")
}

!is returns true if the type doesn’t matches. It’s just a not operator for is operator.

if (shapeObject !is Circle) {
    print("it’s not a Circle")
} else if (shapeObject !is Square) {
    print("it’s not a Square")
} else if (shapeObject !is Rectangle) {
    print("it’s not a Rectangle")
}

Smart Casts

In other programming languages, the variable requires an explicit casting on the variable before accessing the properties of that variable but Kotlin does a smart casting. The compiler automatically converts the variable shapeObject to a particular class reference once it’s passed through any conditional operator.

var area: Float = 0.0f
if (shapeObject is Circle) {
    shapeObject.radius = 10.0f //compiles fine
    area = shapeObject.calculateArea()
} else if (shapeObject is Square) {
    shapeObject.sideLength = 5.0f //compiles fine
    area = shapeObject.calculateArea()
} else if (shapeObject is Rectangle) {
    shapeObject.length = 10.0f //compiles fine
    shapeObject.breadth = 5.0f //compiles fine
    area = shapeObject.calculateArea()
}

The compiler is sufficiently smart to know a cast to be safe if a negative check leads to a return.

if ( shapeObject !is Circle) return
shapeObject.radius = 3.0f /*compiles fine as the non Circle class reference were already returned*/
area = shapeObject.calculateArea()

It even works on the right-hand side of && and ||

/* Automatically cast the right-hand side of && to Circle */
if (shapeObject is Circle && shapeObject.radius > 5.0f){
    print("Circle with radius more than 5.0")
}
/* Automatically cast the right hand side of || to Sqaure */
if (shapeObject !is Square || shapeObject.sideLength < 3.0f){
    print("Either not square or is a square with side length less than 3.0f")
}

it even works with when conditions or while loop

when(shapeObject){
    is Circle -> shapeObject.radius = 3.0f
    is Square -> shapeObject.sideLength = 4.0f
    is Rectangle -> {
       shapeObject.length = 5.0f
       shapeObject.breadth = 6.0f
    }
    else -> print("Undefined type")
}
var count = 0
while (count < 5 && shapeObject is Circle){
    shapeObject.radius = count.toFloat() //compiles fine
    area += shapeObject.calculateArea()
}

Note that smart casts don’t work when the compiler can’t guarantee that the variable can’t change between the check and the usage.

More specifically, smart casts are applicable according to the following rules:

  • val local variables — always
  • val properties — if the property is private or internal or the check is performed in the same module where the property is declared. Smart casts aren’t applicable to open properties or properties that have custom getters
  • var local variables — if the variable is not modified between the check and the usage and is not captured in a lambda that modifies it
  • var properties — never (because the variable can be modified at any time by other code).

Explicit Cast operator ‘as’

as operator works as other languages cast operators which casts the object to another object with particular reference.

var otherShapeObject = shapeObject as Circle

or the nullable type object can only be cast to a new nullable reference type object.

var nullableShapeObject : Circle? = shapeObject as Circle?

The above explicit cast is unsafe as it can throw an exception if the cast is not possible. That’s why as operator called as unsafe cast operator.

Instead, we can use a safe cast operator as? where it assigns a null value if the cast is not possible without throwing an exception.

var safeCastObject : Circle? = shapeObject as? Circle
Comments