Let's kick off today's podcast episode with a quick explanation of what ‘casting' is in general.
Casting exists in today's Object Oriented Programming languages, as it's the means by which we turn one Object type into another Object type. Think of it like turning a caterpillar into a butterfly, only without that annoying cocoon stage.
In Java (and other object oriented programming languages) we can literally take an Object of one type and turn it into another type with just one line of code. I'll get into what that code is in just a moment, but first I want to give you a little more background on casting.
What is Casting All About?
So in Java, casting is all about moving around the hierarchy of an Object. To explain this concept, I'll use this as an example, picture the following hierarchy: Object
-> Mammal
-> Human
. On the most generic end we have the Object
, then we get a little bit more specific with the Mammal
type, and then to the most specific type in our hierarchy the Human
object.
Given this example, what do I mean by “moving around the hierarchy”? Well consider the most generic type in our example, the Object… what sort of operations can you perform on an Object? Can you ask the Object what it's name is? No! It's an Object, it only has a handful of operations that you can perform (namely, toString()
, hashCode()
and equals()
). So do you see how asking an Object what it's name is doesn't really make sense? On the contrary, let's look at the most specific type in our example, the Human
. Does asking the Human
what it's name is make sense? Sure it does, I would assume all Human
s would have a name.
So do you see how the type of Object you're looking at in a particular hierarchy is important when you're deciding what methods to invoke? So, where you are in a particular Object's hierarchy will dictate what you can do.
So how does this apply to casting? Well, casting is what allows you to change the Object
you have into a more specific type of Object
with the goal of gaining access to those additional methods/properties that you want to use.
There are two types of casts that you can do:
- Upcast
- Downcast
Upcasting is the simpler of the two casting options, and this is because it's always safe to upcast. Let's think about this for a moment, if you're given a Human
object, would it make sense for you to upcast that to a Mammal
type? Of course it would, because a Human
is a Mammal
! This should be bringing back thoughts of inheritance in your brain. Inheritance is all about the “is a” relationship. So since a Human
is a Mammal
, then we can easily cast a Human
into a Mammal
.
The side effect of this is that we will lose whatever functionality that was specific to the Human
type. So if the Human
type defined a “name” property, we will no longer be able to access that property. We will still be able to access all the methods and properties that are specific to the Mammal
class though (like eyeColor
for example). And then, since we're still talking about Upcasting, you could upcast your Mammal
variable to be of type Object
if you like. Since a Mammal
is an Object
, the upcast makes perfect sense!
Alright, so that's upcasting, pretty straight-forward. So now let's talk about downcasting. This concept is just like an upcast, only in reverse. We are taking a more generic Object (like a Mammal) and trying to cast it down into a more specific type (like Human). So, all of the things I mentioned before about functionality still holds true here, only this time you will be gaining more functionality rather than losing it (as we're getting more specific in our hierarchy).
What changes, is that downcasting can lead to errors if you're not a bit careful. This goes back to my talk in inheritance about the direction that the inheritance flows. To refresh your memory I said that you could use the following exercise and ask yourself if “ALL” objects of one type would belong to another type. So in our example, we'd ask, “Are all Humans, Mammals?” vs “Are all Mammals, Humans?”. Well the answer is, “Yes all Humans are Mammals” and “No, not all Mammals are Humans”. So because of this, it's safe to cast any Human into the category of Mammal, whereas it's NOT safe to cast any Mammal into the category of Human! You see where I'm going with this?
So since not all Mammals are Humans, if we try to downcast a Mammal to a Human, we should be sure that the Object in question really is a Human, otherwise we'll get a ClassCastException. Now there is a way to verify if your Object is indeed a Human before you try to cast it, but I'll touch on that later.
Why Would we Cast an Object?
So all this talk about the implications of casting, but I haven't yet explained WHY we would cast. There are a couple of reasons why one would need to perform a cast in Java.
- Casting to a more specific
Object
type to get access to additional functionality - Casting to a different variable type in order to comply with the type restrictions on a method or constructor
The first reason we've already touched on, but let's use a more common example than the Human
/Mammal
example. I'm sure at this point you're familiar with what a Collection
is, if not then I'd suggest you go back in the podcast history and listen to more shows… if you listened to that show already but just forget, I'll jog your memory! A Collection
is Java's generic interface that is used to represent its data structures. Common Collection
s in Java are the List
, Set
or Map
.
So let me give you another example of a type hierarchy: Collection
-> List
-> ArrayList
. What would happen if you had a method defined that takes a generic Collection
type, and within the method you want to use the functions of an ArrayList
? Well, you wouldn't be able to access all the methods in an ArrayList
until you cast the variable from the generic Collection
type to be an ArrayList
(just like casting an Object
to be a Human
).
Fair enough, so what about the second reason for why we would cast an Object
? Sometimes you need to comply with the types that are defined in certain methods. Normally, if a programmer is properly programming to an interface, then you should be safe with passing in your specific object type (as it will just upcast automatically for you)… but, there are some cases where a method will take a very specific parameter type, and you'll need to cast your variable in order to comply with the method signature. An example of this would be if a method specifically needed an ArrayList
, and the variable you want to pass in is declared as a (more generic) List type. This type of casting doesn't happen often, but I've definitely encountered it before.
So HOW do you Cast Already!?
Okay so now that you're familiar with the concept of casting, how do we actually accomplish it in code? Well it's pretty simple really, all you need to do is figure out what variable type you want to cast to, and then include it in parenthesis before your variable.
So let's talk about the List/ArrayList example. Let's say you have two variables, one is called myArrayList
, the other is called yourList
. Let's assume that myArrayList
is defined as an ArrayList
and yourList
is defined as a List
. You with me so far? Now let's say that we want to assign myList
to myArrayList
, you could try to write:
ArrayList myArrayList = new ArrayList(); List myList = new ArrayList(); // this will cause a compilation error myArrayList = myList;
But you will get a compilation error stating that you'll need to cast myList
to the ArrayList
type. So to fix this, all you need to is add (ArrayList)
before the myList
variable, which indicates that you want to case myList
to the ArrayList
type.
The correct code would look like this:
ArrayList myArrayList = new ArrayList(); List myList = new ArrayList(); // this will cause a compilation error myArrayList = (ArrayList)myList;
Quick Tip: If you are downcasting and you want to get access to the more specific methods/properties, just add another set of parenthesis around the variable and cast. For example ((ArrayList)myList).ensureCapacity(5)
, in this case, the ensureCapacity()
method is not available for a List
, just an ArrayList
.
Type Checking Before Casting
If you want to do a bit of testing to ensure that a cast will work in code, you can use the instanceof
keyword. So an example of this would be:
if (mammalVar instanceof Human) { Human aHuman = (Human)mammalVar; }
The if
statement above will evaluate to true
if the mammalVar
is of type Human
. So this is the kind of code you would use to ensure safe casting in Java.
Fun with Casting
One thing that you can try out yourself is trying to convert one primitives type into another primitive type. For example, what do you think happens when you have a double
(which is a number with a decimal value) and try to cast the double
to an int
? Or vice-versa?
You'll just have to try it out for yourself and see what happens!