Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why does this code with generics throw a ClassCastException in Java 11?

Tags:

java

generics

The code below succeeds in Java 8 but throws a ClassCastException in Java 11. Why did the behavior change?

I could not find any related changes in OpenJDK's Java 9, Java 10 or Java 11 feature sets.

public class GenericsExample {

    public static void main(String[] args) {
        Set<Car> set = new HashSet<>();
        set.add(getAnimal());
    }

    static <T extends Animal> T getAnimal() {
        return (T) new Animal() {};
    }

    interface Animal {}

    class Car {}
}
like image 406
jayde Avatar asked Jan 23 '19 19:01

jayde


People also ask

What causes ClassCastException in Java?

ClassCastException is a runtime exception raised in Java when we try to improperly cast a class from one type to another. It's thrown to indicate that the code has attempted to cast an object to a related class, but of which it is not an instance.

How do I fix Java Lang ClassCastException?

To prevent the ClassCastException exception, one should be careful when casting objects to a specific class or interface and ensure that the target type is a child of the source type, and that the actual object is an instance of that type.

What code will cause a ClassCastException to be thrown?

If we write code like this: String obj = (String) hmp. get(key); it would throw a class cast exception, because the value returned by the get method of the hash map would be an Array list, but we are trying to cast it to a String.

Which method throws ClassCastException?

ClassCast Exception is thrown when we try to cast an object of the parent class to the child class object. However, it can also be thrown when we try to convert the objects of two individual classes that don't have any relationship between them.


Video Answer


1 Answers

Indeed it was a bug in Java 8, which was fixed in Java 9 - bugfix. In some scenarios, the javac CHECKCAST instruction was skipped.

If you are curious, consider those 2 lines of code:

Set<Car> set = new HashSet<>(); // line 11
set.add(getAnimal());           // line 12

Java 8 bytecode will look like this:

 LINENUMBER 11 L1
 ALOAD 1
 ALOAD 0
 INVOKEVIRTUAL UserManagerTest.getAnimal ()LUserManagerTest$Animal;
 INVOKEINTERFACE java/util/Set.add (Ljava/lang/Object;)Z (itf)
 POP

But Java 9 will look like this:

LINENUMBER 11 L1
ALOAD 1
ALOAD 0
INVOKEVIRTUAL UserManagerTest.getAnimal ()LUserManagerTest$Animal;
CHECKCAST UserManagerTest$Car
INVOKEINTERFACE java/util/Set.add (Ljava/lang/Object;)Z (itf)
POP

The only difference is CHECKCAST instruction, which (according to JavaDoc) states that the named class, array, or interface type is resolved. If object can be cast to the resolved class, array, or interface type, the operand stack is unchanged; otherwise, the checkcast instruction throws a ClassCastException.

like image 70
Piotr Niewinski Avatar answered Oct 06 '22 00:10

Piotr Niewinski