Java Reference Type Casting

In this tutorial we will examine reference type casting in Java. We will look at different techniques such as upcasting, downcasting, and approaches to performing safe type casting in Java. Casting variables in Java is an important topic for every Java Programmer.

Type Casting In Java

Type Casting in Java is converting a Java Object of one type and turning it into an Object of another Type. Casting is a common task that a programmer normally performs, and just as with other languages you cannot cast a variable to any type.

Java Reference Casting

In this tutorial we will focus on the issues and rules related to casting reference types in Java. As you are probably aware a reference variable holds a pointer – a memory location, to an Object. The reference variable does not store the Object.

When we type cast a reference of a certain type to another type, we don’t modify the object itself, but we convert the reference to a certain type that we can work with. We can convert it to a more specific type by downcasting it, such as an Object to a String. Alternatively we can convert it to a more generic type by upcasting, such as converting a String to an Object.

Java Upcasting

Upcasting is the process of converting a subtype to a supertype. Typically upcasting is performed by the compiler implicitly.

1
2
String str = "A String";
Object obj = str;

In the above example we cast a String to an Object. Moreover, the compiler performed the cast implicitly for us, we did not need to use any special operation / instruction to perform the cast.

The process of upcasting performs a narrowing operation on the type. For example when we cast the variable str to the variable obj of type Object we lose the functionality provided by the String class. We can no longer access the methods and properties of the String class, instead we only have the method and properties made available through the Object type.

Upcasting is used with inheritance hierarchies. It is a common programming task to use reference variables to refer to supertypes – more specific type. When we perform this technique the compiler implicitly performs the cast for us. For example consider the following vehicle class hierarchy:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
public abstract class Vehicle {

    public final int numberOfWheels;

    public Vehicle(final int numberOfWheels) {
        this.numberOfWheels = numberOfWheels;
    }

    public void startEngine() {
        System.out.println("Engine Started");
    }

    public void stopEngine() {
        System.out.println("Engine Stopped");
    }
}

Vehicle is the root of our hierarchy we now extend Vehicle with the Motorbike type.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
public class MotorBike extends Vehicle {

    public MotorBike() {
        super(2);
    }

    public void backBrake() {
        System.out.println("Used Back Brake");
    }

    public void frontBrake() {
        System.out.println("Pulled Front Brake");
    }
}

We can create an instance of type MotorBike:

1
MotorBike bike = new MotorBike();

We can also implicitly cast it to an Object of type Vehicle:

1
Vehicle vehicle = bike;

The type conversion in the above code is performed implicitly by the compiler. We can also perform the type cast explicitly.

1
Vehicle vehicle2 = (Vehicle) bike;

However, its not a good practice to perform the explicit cast when the compiler does it for us for free.

It is important to note that we can upcast any subtype to its supertype implicitly. For example if we create another subclass of Vehicle:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
public class Car extends Vehicle {

    public Car() {
        super(4);
    }

    public void openTrunk() {
        System.out.println("Trunk Opened");
    }

    public void brake() {
        System.out.println("Brake applied");
    }

    public void handbrake() {
        System.out.println("Handbrake applied");
    }
}

We can also implicitly type cast the Car to its parent Vehicle type.

1
2
Car car = new Car();
Vehicle vehicle3 = car;

As we mentioned, when we perform Java upcasting we lose the ability to use the methods and properties of the subtype. For example if we attempted to call the method handbrake on the variable of type Vehicle:

1
vehicle3.handbrake();

it will result in a compile time error. Although, the object that vehicle3 points to is still of type Car we are accessing it through a variable of type Vehicle therefore we only have the properties and methods available of that type.

If we want to call the method handbrake on the vehicle3 variable we must downcast it to the type Car:

1
((Car) vehicle3).handbrake();

Java Downcasting

In the previous section we looked at upcasting in Java in this section we will look at the opposite of upcasting commonly known as downcasting.

Downcasting is the process of converting a supertype to its subtype. It is also known as widening of the type, as the converted type gains the methods and properties of the type it is converted to.

1
Vehicle honda = new MotorBike();

Above we create a new instance of MotorBike and assign it to a variable honda of type Vehicle. If we try to call methods of the MotorBike class on this variable, it will result in a compile time error.

1
honda.backBrake()

Since we know that the type of Object that honda refers to is of type MotorBike we can downcast it, and call the backBrake method.

1
((MotorBike) honda).backBrake();

In summary, we can only downcast to a subtype if the type of Object pointed to by the variable is of that type. For example if we try to do the following:

1
2
Vehicle yamaha = new MotorBike();
Car suzuki = (Car) yamaha;

The code will compile fine, but if we try to run the above, it will result in a RuntimeException.

1
2
Exception in thread "main" java.lang.ClassCastException: com.stephenenright.tutorials.corejava.casting.MotorBike cannot be cast to com.stephenenright.tutorials.corejava.casting.Car
    at com.stephenenright.tutorials.corejava.casting.ReferenceTypeCastingExamples.main(ReferenceTypeCastingExamples.java:29)

ClassCastException

As we saw in the last example when we attempted to cast a MotorBike to a Car the program threw an exception of type ClassCastException.

ClassCastException is thrown at runtime when we attempting to downcast to a type that is not of the type we are attempting to downcast to. The compiler does not complain because the variable yamaha is of type Vehicle which is a supertype of the variable suzuki of type Car.

However, if we attempt to cast a type to an unrelated type as shown below, the compiler will complain with compile errors.

1
2
MotorBike yamaha = new MotorBike();
Car suzuki = (Car) yamaha; 

In order to cast one type to another without compile errors both types are required to be in the same inheritance tree.

Java Instanceof Operator

In order to safely perform downcasting in Java we need to test if an Object is of a specific type. This is important to prevent errors such as the ClassCastException.

1
2
3
4
5
6
7
8
Vehicle yamaha = new MotorBike();

if(yamaha instanceof Car) {
    Car yamahaCar  = (Car) yamaha;
}
else {
    System.out.println("yamaha is not a car");
}

In the above code we use the instanceof operator to test if the type held in the variable yamaha is of type Car. If we run the above code we get the following output.

1
yamaha is not a car

Java isInstance Method

The isInstance method is another means of performing safe type casting in Java. Just like the instanceof operator, the isInstance method can be used to test if a variable can be assigned to specific type.

1
2
3
if (Car.class.isInstance(suzuki)) {
    System.out.println("suzuki is a car");
}

When executed, the code outputs:

1
suzuki is a car

Cast() Method

Another approach to casting between types is to use the cast method.

1
2
3
if (Car.class.isInstance(suzuki)) {
    Car suzukiCar = Car.class.cast(suzuki);
}

The Java cast method is commonly used with generic types. For example suppose we want to write a component to collect traffic.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
public class TrafficCollector<T> {

    public Class<T> type;

    public TrafficCollector(Class<T> type) {
        this.type = type;
    }

    public List<T> collectTraffic(List<Vehicle> traffic) {
        List<T> collectedTraffic = new LinkedList<T>();
        for (Vehicle vehicle : traffic) {
            if (type.isInstance(vehicle)) {
                T objAsType = type.cast(vehicle);
                collectedTraffic.add(objAsType);
            }
        }
        return collectedTraffic;
    }
}

We can use the isInstance and cast methods to collect all of the cars found in the traffic.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
@Test
public void collectTraffic() {
    List<Vehicle> traffic = new LinkedList<Vehicle>();
    traffic.add(new Car());
    traffic.add(new MotorBike());
    traffic.add(new Car());
    traffic.add(new MotorBike());

    TrafficCollector<Car> carCollector = new TrafficCollector<Car>(Car.class);

    List<Car> collectedTraffic = carCollector.collectTraffic(traffic);

    for (Car car : collectedTraffic) {
        assertTrue("Expected to be of type car", car instanceof Car);
    }
}

Conclusion

In this short tutorial we covered some of the issues and techniques when casting reference types in Java. We looked at upcasting, downcasting, the Java cast method, and techniques to prevent errors when casting types in Java.