Casting and instanceof
The "Animal" remote is pointed at a dog, but it has no "Bark" button, so a.bark() does not compile. To make the dog bark, you need the "Dog" remote. In Java you get that remote by casting: you take the same reference and look at it as a reference of type Dog.
public class Main {
public static void main(String[] args) {
Animal a = new Dog();
Dog d = (Dog) a;
d.bark(); // Woof
}
}
class Animal {}
class Cat extends Animal {}
class Dog extends Animal {
void bark() { System.out.println("Woof"); }
}Let's walk through the program step by step:
Animal a = new Dog();puts a reference to a dog into a variable of typeAnimal. That is the "Animal" remote.(Dog) ais a cast: a type in parentheses in front of the reference. This is how we tell the compiler: "there is a dog behind this reference".Dog d = (Dog) a;puts the same reference into a variable of typeDog. That is the "Dog" remote.d.bark()printsWoof: the typeDoghas a methodbark.
The parentheses are familiar from numbers: (int) 3.7 gives 3. But with numbers a cast changes the value, and with references it does not. The object is neither changed nor copied: a and d lead to one and the same dog, and only the remote changes.
Up and down the hierarchy
The parent is usually drawn on top, with its subclasses below it. That is where the names of the two casts come from.
Upcasting
Upcasting goes from a subclass to its parent: Animal a = new Dog();. Java does it by itself, without parentheses. A dog is always an animal, so nothing can go wrong here.
Downcasting
Downcasting goes from a parent to a subclass: (Dog) a. It is written explicitly, because an animal is not always a dog. The compiler sees only the type Animal and takes your word for it, and the JVM checks the cast when the program reaches that line.
If there turns out to be a cat behind the reference, the JVM throws a ClassCastException and stops the program at the line with the cast. A cast that can never work is rejected by the compiler already: a variable of type Cat cannot be cast to Dog, and the compiler answers incompatible types: Cat cannot be converted to Dog.
The instanceof check
Instead of guessing, you check an object's type with the instanceof operator. It gives true if the object belongs to that class or to a subclass of it:
Animal b = new Cat();
System.out.println(a instanceof Dog); // true
System.out.println(b instanceof Dog); // false
System.out.println(b instanceof Animal); // trueLine by line:
a instanceof Doggivestrue: there is a dog behinda.b instanceof Doggivesfalse: there is a cat behindb.b instanceof Animalgivestrue: a cat is an animal too.
For null the answer is always false: there is no object behind an empty reference.
The pattern instanceof Dog dog
Before, a check had to be followed by a separate cast: if (b instanceof Dog) { Dog dog = (Dog) b; … }. Since Java 16 the check and the new variable go into one expression:
if (b instanceof Dog dog) {
dog.bark();
} else {
System.out.println("not a dog"); // not a dog
}The expression b instanceof Dog dog is called a pattern. It checks the type and, if the check passes, declares a variable dog of type Dog holding the same reference. The variable dog is visible only where the check has surely passed: in this example, in the if branch. Here there is a cat behind b, so the else branch runs.
Interviewers often ask when a ClassCastException happens. The answer: on a downcast, when the object really belongs to a different class. The protection against it is an instanceof check before the cast.
Here the remote picture is off. In real life you can point a "Dog" remote at a cat: the "Bark" button does nothing. Java does not work like that: a wrong cast stops the program at once, at the line with the cast.
Common mistake
A reference was cast down without checking what is behind it:
Animal pet = new Animal();
Dog rex = (Dog) pet; // ✗Exception in thread "main" java.lang.ClassCastException: class Animal cannot be cast to class Dog (Animal and Dog are in unnamed module of loader 'app')The program compiles: pet is declared as Animal, and an animal may turn out to be a dog. But behind pet there is an object of Animal itself, and the JVM throws a ClassCastException.
The message names both classes: class Animal cannot be cast to class Dog. In the parentheses the JVM writes who loaded the classes. This is how the message looks after javac and java Main; when you run java Main.java, the parentheses hold a different name.
Check the type before casting: if (pet instanceof Dog rex) { … }.
Key points
- Upcasting,
Animal a = new Dog(), Java does by itself, and downcasting,(Dog) a, is written explicitly. - A cast changes only the type of the reference, and the object stays the same.
- If the object belongs to a different class, a downcast throws a
ClassCastExceptionwhile the program runs. x instanceof Dogchecks the object's type, andif (x instanceof Dog d)also declares a variabledof typeDog.