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).

This post was originally posted at https://agrawalsuneet.github.io/blogs/typecheck-is-and-cast-as-in-kotlin/ and reposted on Medium on 24th Nov 2017.

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 class 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’ 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”)
}

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).

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 operatoras?’ where it assigns a null value if the cast is not possible without throwing an exception.

var safeCastObject : Circle? = shapeObject as? Circle

That’s all for now. You can read my other interesting posts here or you can enjoy my games or apps listed here. Feel free to use my open-source Android components in your app listed here. Or drop an email, if you didn’t find what you are looking for and need some help.