1. Overview
In this short tutorial, weβll focus on ClassCastException, a common Java exception.
ClassCastException is an unchecked exception that signals the code has attempted to cast a reference to a type of which itβs not a subtype.
Letβs look at some scenarios that lead to this exception being thrown and how we can avoid them.
2. Explicit Casting
For our next experiments, letβs consider the following classes:
public interface Animal {
String getName();
}
public class Mammal implements Animal {
@Override
public String getName() {
return "Mammal";
}
}
public class Amphibian implements Animal {
@Override
public String getName() {
return "Amphibian";
}
}
public class Frog extends Amphibian {
@Override
public String getName() {
return super.getName() + ": Frog";
}
}
2.1. Casting Classes
By far, the most common scenario for encountering a ClassCastException is explicitly casting to an incompatible type.
For example, letβs try to cast a Frog to a Mammal:
Frog frog = new Frog();
Mammal mammal = (Mammal) frog;
We might expect a ClassCastException here, but in fact, we get a compilation error: βincompatible types: Frog cannot be converted to Mammalβ. However, the situation changes when we use the common super-type:
Animal animal = new Frog();
Mammal mammal = (Mammal) animal;
Now, we get a ClassCastException from the second line:
Exception in thread "main" java.lang.ClassCastException: class Frog cannot be cast to class Mammal (Frog and Mammal are in unnamed module of loader 'app')
at Main.main(Main.java:9)
A checked downcast to Mammal is incompatible from a Frog reference because Frog is not a subtype of Mammal. In this case, the compiler cannot help us, as the Animal variable may hold a reference of a compatible type.
Itβs interesting to note that the compilation error only occurs when we attempt to cast to an unequivocally incompatible class. The same is not true for interfaces because Java supports multiple interface inheritance, but only single inheritance for classes. Thus, the compiler canβt determine if the reference type implements a specific interface or not. Letβs exemplify:
Animal animal = new Frog();
Serializable serial = (Serializable) animal;
We get a ClassCastException on the second line instead of a compilation error:
Exception in thread "main" java.lang.ClassCastException: class Frog cannot be cast to class java.io.Serializable (Frog is in unnamed module of loader 'app'; java.io.Serializable is in module java.base of loader 'bootstrap')
at Main.main(Main.java:11)
2.2. Casting Arrays
Weβve seen how classes handle casting, now letβs look at arrays. Array casting works the same as class casting. However, we might get confused by autoboxing and type-promotion, or lack thereof.
Thus, letβs see what happens for primitive arrays when we attempt the following cast:
Object primitives = new int[1];
Integer[] integers = (Integer[]) primitives;
The second line throws a ClassCastException as autoboxing doesnβt work for arrays.
How about type promotion? Letβs try the following:
Object primitives = new int[1];
long[] longs = (long[]) primitives;
We also get a ClassCastException because the type promotion doesnβt work for entire arrays.
2.3. Safe Casting
In the case of explicit casting, it is highly recommended to check the compatibility of the types before attempting to cast using instanceof.
Letβs look at a safe cast example:
Mammal mammal;
if (animal instanceof Mammal) {
mammal = (Mammal) animal;
} else {
// handle exceptional case
}
3. Heap Pollution
As per the Java Specification: βHeap pollution can only occur if the program performed some operation involving a raw type that would give rise to a compile-time unchecked warningβ.
For our experiment, letβs consider the following generic class:
public static class Box<T> {
private T content;
public T getContent() {
return content;
}
public void setContent(T content) {
this.content = content;
}
}
We will now attempt to pollute the heap as follows:
Box<Long> originalBox = new Box<>();
Box raw = originalBox;
raw.setContent(2.5);
Box<Long> bound = (Box<Long>) raw;
Long content = bound.getContent();
The last line will throw a ClassCastException as it cannot transform a Double reference to Long.
4. Generic Types
When using generics in Java, we must be wary of type erasure, which can lead to ClassCastException as well in some conditions.
Letβs consider the following generic method:
public static <T> T convertInstanceOfObject(Object o) {
try {
return (T) o;
} catch (ClassCastException e) {
return null;
}
}
And now letβs call it:
String shouldBeNull = convertInstanceOfObject(123);
At first look, we can reasonably expect a null reference returned from the catch block. However, at runtime, due to type erasure, the parameter is cast to Object instead of String. Thus the compiler is faced with the task of assigning an Integer to String, which throws ClassCastException.
5. Conclusion
In this article, we have looked at a series of common scenarios for inappropriate casting.
Whether implicit or explicit, casting Java references to another type can lead to ClassCastException unless the target type is the same or a descendent of the actual type.
