A bad casting to Generics parameter type does not throw ClassCastException in Java

不想你离开。 提交于 2020-06-27 08:37:09

问题


So, I have rather esoteric question. I'm trying to create a somewhat generic, but typed property collection system. It's reliant on a core assumption that seems to be erroneous. The code illustrates the issue:

import java.lang.Integer;

public class Test {
    private static Object mObj = new String("This should print");

    public static void main(String[] args ) {
    String s = Test.<String>get();
    System.out.println(s);

        try {
        // actual ClassCastException reported HERE
        int i = Test.<Integer>get();
    } catch ( ClassCastException e ) {
        System.out.println("Why isn't the exception caught earlier?");
        }

        int i2 = getInt();
    }

    public static <T> T get() {
    T thing = null;
    try {
        // Expected ClassCastException here
        thing = (T)mObj;
    } catch ( ClassCastException e ) {
        System.out.println("This will *not* be printed");
    }
    return thing;
    }

    // This added in the edit
    public static Integer getInt() {
    return (Integer)mObj;
    }
}

After compiling and running the output is

This should print
Why isn't the exception caught earlier?

In the static method "get", I'm attempting to cast to the generic parameter type T. The underlying member (mObj) is of String type. In the first invocation, the Generic parameter is of compatible type, so the app prints the string appropriately.

In the second invocation, the Generic parameter is of type Integer. Thus, the cast in the get method should fail. And I would hope it would throw a ClassCastException (printing the statement "This will *not* be printed".) But this isn't what happens.

Instead, the casting exception is thrown after the get method returns when the returned value is attempted to be assigned to the variable "i". Here's the question:

What is the explanation for this delayed casting error?

** EDIT ** For the sake of fun and completeness, I added a non-generic getInt method to illustrate the response I was hoping to get. Amazing what happens when the compiler knows type.


回答1:


It's because the cast in the get method (which should generate a compiler warning) is an unchecked cast. The compiler does not know what T is when you call thing = (T) mObj; so it cannot know if the cast should not compile.

After compilation, and due to type erasure, the bytecode generated invokes the method get which returns an Object (T is erased and replaced with Object) and the cast is simply translated to:

Object thing = null;
try {
    thing = mObj;

The check is being done after the result is returned from the get method, i.e. int i = Test.<Integer> get(); is equivalent to:

Object o = Test.get();
int i = ((Integer) o).intValue();



回答2:


That is probably because in the runtime the jvm doesn't know what T is, and it treats it as a Object, so the actual cast to the Integer is going on when the generic type is known, so here: int i = Test.<Integer>get();




回答3:


Generics in java are implemented with erasures. This means that at runtime java has no meaning of the generic types used and uses Object instead.

At runtime "thing" is an Object, and assigning anything to an Object doesn't provoke any error.




回答4:


public static <T> T get()

insists that the result will be of caller type. Thus line

int i = Test.<Integer>get();

forces Integer to obj to be cases as Integer:

thing = (T)mObj;

But mObj is an instance of String.




回答5:


To implement generics, the Java compiler applies type erasure to:

Replace all type parameters in generic types with their bounds or Object if the type parameters are unbounded.

ref:http://docs.oracle.com/javase/tutorial/java/generics/erasure.html

Hence, the code

public static <T> T get() {
T thing = null;
try {
    // Expected ClassCastException here
    thing = (T)mObj;
} catch ( ClassCastException e ) {
    System.out.println("This will *not* be printed");
}
return thing;
}

at runtime should erase to be

public static Object get() {
Object thing = null;
try {
    // Expected ClassCastException here
    thing = (Object)mObj;
} catch ( ClassCastException e ) {
    System.out.println("This will *not* be printed");
}
return thing;
}

since T is unbounded. Understand this, then it is easy to explain the output.



来源:https://stackoverflow.com/questions/37207811/a-bad-casting-to-generics-parameter-type-does-not-throw-classcastexception-in-ja

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!