In this section, we will learn what upcasting and downcasting is and how to use it in Kotlin.
Note: we’re assuming you’re already familiar with the Kotlin inheritance.
What is Upcasting in Kotlin?
Upcasting is the idea of creating an object from a child class and storing that object into a variable that is of type super class!
This is possible because a child class is a parent class (Check the Kotlin Inheritance section if you’re not familiar with the “is – a” relationship).
Now the important thing to remember is that when an object of type child class is stored in a variable of type parent class, we can only access those members of the object that are shared between the child and parent class! This means if the object has created more functions in its body, then using a variable of parent class, we can’t access those functions anymore.
Example: upcasting in Kotlin
open class Machine{ open fun start(){ println("The machine is started") } } class Camera: Machine(){ override fun start(){ println("Camera started") } fun takePicture(){ println("Camera took picture") } } fun main(){ var cam: Machine = Camera() cam.start() }
Output:
Camera started
How does upcasting work in Kotlin?
In this example, we’ve created a new object from the Camera class and stored it in a variable of type Machine class which is the parent of the Camera.
So here we have only the access to the start
function because it is shared between both classes. But if we tried to access the takePicture
function, we would’ve got error because the function is only defined in the Camera class and not the Machine.
Kotlin the is Operator: What is Downcasting in Kotlin?
So far, we’ve seen that when upcasting, only the shared members of both classes will be accessible using the variable of parent type.
But what if we’ve stored an object of a child type in a variable of type parent and then we wanted to cast (convert) that back into the child type?
Let’s consider the example above:
Here, you can’t create a variable of type Camera and store whatever is in the cam
variable (which is of type Machine) into the new variable of type Camera! To the eyes of your program, you’re trying to assign a Machine data type to a Camera data type and it will return an error instead because a Camera type is neither the parent of a Machine nor it is the same type as Machine!
var cam: Machine = Camera() var cam2:Camera = cam // This is a source of error.
But we know that bottom line, the cam
variable is referencing an object of type Camera here! So if we could somehow tell our program that the actual object that the cam variable is pointing to is of the same data type as the cam2
data type, then the problem is solved.
In Kotlin, you can do that using the is
operator, which is known as smart cast!
This is the syntax of the is operator:
object is data-type
Here, the object
is actually the variable that is pointing to an object. (For example, the cam variable from the last example could be used here).
data-type
: this is the class name that we want to see if the object is of that type!
The return value of this operator is a boolean value. If the returned value is true, it means the variable is of data type mentioned on the right side of the operator. Otherwise the value false will return.
Now if we use the is
operator as the condition of an if statement and the result of this operator becomes true (so that the body of the if statement runs) then within the body of the if statement, the object
will be treated as if its data type is the one on the right side of the is
operator (Basically the is operator will automatically cast the variable to the data type we set on the right side of the is operator).
It might seem a little confusing at first, but seeing the example below would help you to see the is operator in practice.
Example: downcasting in Kotlin using the is operator
open class Machine{ open fun start(){ println("The machine is started") } } class Camera: Machine(){ override fun start(){ println("Camera started") } fun takePicture(){ println("Camera took picture") } } fun main(){ var cam: Machine = Camera() if (cam is Camera){ cam.takePicture() } }
Output:
Camera took picture
As you can see, within the body of the if
statement we could call the takePicture
function because the is operator automatically converted (AKA cast) the cam variable into the type Camera
and so the takePicture
function is now available for us to use.
But remember, this casting is only available in the body of the if statement! Outside of this border, we can’t access the takePicture() function anymore!
Kotlin the as Operator
The as operator works the same as the is
operator, with the exception that we use it to explicitly cast a variable into another data type!
For example, if we want to convert the cam
variable in our example into a data type of Camera
, then we can use this as
operator to explicitly run the casting.
This is the syntax of this operator:
variable as data-type
Here, the variable is the one that we want to cast its data type and the data-type
is the target type we want to cast to.
Note that the object that the variable is pointing to must be of the same type as the data-type
we put on the right side of the `as` operator! Otherwise we will get an error.
The return value of this operator (on a successful operation) is a reference to the target object that could be stored in a variable of type data-type
.
Example: using the as Operator
open class Machine{ open fun start(){ println("The machine is started") } } class Camera: Machine(){ override fun start(){ println("Camera started") } fun takePicture(){ println("Camera took picture") } } fun main(){ var cam: Machine = Camera() var cam2: Camera = cam as Camera cam2.takePicture() }
Output:
Camera took picture