Skip to content
Theory · Inheritance and polymorphism · lesson 9 of 10

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.

JAVA
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:

  1. Animal a = new Dog(); puts a reference to a dog into a variable of type Animal. That is the "Animal" remote.
  2. (Dog) a is 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".
  3. Dog d = (Dog) a; puts the same reference into a variable of type Dog. That is the "Dog" remote.
  4. d.bark() prints Woof: the type Dog has a method bark.

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:

JAVA
Animal b = new Cat();
System.out.println(a instanceof Dog);    // true
System.out.println(b instanceof Dog);    // false
System.out.println(b instanceof Animal); // true

Line by line:

  1. a instanceof Dog gives true: there is a dog behind a.
  2. b instanceof Dog gives false: there is a cat behind b.
  3. b instanceof Animal gives true: 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:

JAVA
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:

JAVA
Animal pet = new Animal();
Dog rex = (Dog) pet; // ✗
TEXT
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 ClassCastException while the program runs.
  • x instanceof Dog checks the object's type, and if (x instanceof Dog d) also declares a variable d of type Dog.