I have some code which looks something like this (part of a negative test of the method get
):
import java.util.*;
public class Test {
Map<String, Object> map = new HashMap<>();
public static void main (String ... args) {
Test test = new Test();
test.put("test", "value"); // Store a String
System.out.println("Test: " + test.get("test", Double.class)); // Retrieve it as a Double
}
public <T> T get(String key, Class<T> clazz) {
return (T) map.get(key);
}
public void put(String key, Object value) {
map.put(key, value);
}
}
I was expecting it to throw a ClassCastException
but it runs through successfully printing:
Test: value
Why doesn't it throw?
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.
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.
The Java compiler won't let you cast a generic type across its type parameters because the target type, in general, is neither a subtype nor a supertype.
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.
Runtime exceptions are an inevitable evil that all Java programmers have to face at some point. One of these exceptions is the ClassCastException which is thrown whenever there is an attempt to cast an object to a class or an interface the object is incompatible with.
Typecasting is the assessment of the value of one primitive data type to another type. In java, there are two types of casting namely upcasting and downcasting as follows:
Explicit casting means class typecasting done by the programmer with cast syntax. Downcasting refers to the procedure when subclass type refers to the object of the parent class is known as downcasting. If it is performed directly compiler gives an error as ClassCastException is thrown at runtime.
In order to perform class type casting we have to follow these two rules as follows: An object must have the property of a class in which it is going to cast. Output explanation: Here parent class object is called but referred to the child’s class object. Hence, one can relate this with dynamic polymorphism or function overriding.
That's because you are casting to the generic type T
, which is erased at runtime, like all Java generics. So what actually happens at runtime, is that you are casting to Object
and not to Double
.
Note that, for example, if T
was defined as <T extends Number>
, you would be casting to Number
(but still not to Double
).
If you want to do some runtime type checking, you need to use the actual parameter clazz
(which is available at runtime), and not the generic type T
(which is erased). For instance, you could do something like:
public <T> T get(String key, Class<T> clazz) {
return clazz.cast(map.get(key));
}
I found a difference in the byte code when a method is called on the returned "Double" and when no method is called.
For example, if you were to call doubleValue()
(or even getClass()
) on the returned "Double", then the ClassCastException
occurs. Using javap -c Test
, I get the following bytecode:
34: ldc #15 // class java/lang/Double
36: invokevirtual #16 // Method get (Ljava/lang/String;Ljava/lang/Class;)Ljava/lang/Object;
39: checkcast #15 // class java/lang/Double
42: invokevirtual #17 // Method java/lang/Double.doubleValue:()D
45: invokevirtual #18 // Method java/lang/StringBuilder.append:(D)Ljava/lang/StringBuilder;
The checkcast
operation must be throwing the ClassCastException
. Also, in the implicit StringBuilder
, append(double)
would have been called.
Without a call to doubleValue()
(or getClass()
):
34: ldc #15 // class java/lang/Double
36: invokevirtual #16 // Method get:(Ljava/lang/String;Ljava/lang/Class;)Ljava/lang/Object;
39: invokevirtual #17 // Method java/lang/StringBuilder.append (Ljava/lang/Object;)Ljava/lang/StringBuilder;
There is no checkcast
operation, and append(Object)
is called on the implicit StringBuilder
, because after type erasure, T
is just Object
anyway.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With