简体   繁体   中英

Type safety with generics in Java

I've encountered a behaviour of generics in Java that I completely can't understand (with my .NET background).

public class TestGeneric<T>
{
    public void get (Object arg)
    {
        T temp = (T) arg;

        System.out.println(temp.toString());

        return;
    }
}

TestGeneric<Integer> tg = new TestGeneric<Integer>();
tg.get("Crack!!!");

Please tell me why I'm not getting ClassCastException in get, moreover, in Idea I see temp as String after assignment and having value of "Crack!!!" . Also, how could I have that ClassCastException throwed? I'm using JDK 1.7.0_07 on Windows 7 x64.

The reason you are not getting a class cast exception is that Java generics are implemented through type erasure. Unlike .NET generics that required significant changes to CLS, Java generics are processed entirely in compile-time. At runtime, the cast to T is ignored. In order to check type at runtime, you need to store Class<T> , and use its methods to check the type of a parameter passed in:

public class TestGeneric<T>
{
    private Class<T> genClass;
    public TestGeneric(Class<T> t) {genClass = t;}
    public void get (Object arg)
    {
        if (!genClass.isInstance(arg)) {
            throw new ClassCastException();
        }
        T temp = (T) arg;

        System.out.println(temp.toString());

        return;
    }
}

TestGeneric<Integer> tg = new TestGeneric<Integer>(Integer.class);
tg.get("Bang!!!"); // Now that's a real Bang!!!

This is because the generic type T has no defined bounds so it is treated as an Object. Casting something to T will not cause a ClassCastException in this case.

However, if your class definition was public class TestGeneric<T extends Number> , then you would get the ClassCastException if you passed in a String into get().

It is an instructive exercise to think about what the non-generic code that the generic code is "erased to" looks like:

public class TestGeneric
{
    public void get (Object arg)
    {
        Object temp = (Object) arg;

        System.out.println(temp.toString());

        return;
    }
}

TestGeneric tg = new TestGeneric();
tg.get("Crack!!!"); // should there be any problem?

This is due to type-erasure . This means that in Java all generics are reduced to Object at run-time. So you have casted your String to Object which is totally fine. And since toString is implemented on Object , again no exception.

Here is a link on type-erasure

The only way to really get the ClassCastException is to pass an instance of Class<T> to the generic type and then do myClass.isInstance(arg) and throw an exception if false

Always remember Generics in Java are compile time entities. It has nothing to do at runtime. Let me demo your oqn code to you.

public class TestGeneric<T>
{
    public void get (Object arg)
    {
        T temp = (T) arg;

        System.out.println(temp.toString());
        System.out.println(temp.getClass());

        return;
    }

    public static void main(String args[])
    {
        TestGeneric<Integer> tg = new TestGeneric<Integer>();
        tg.get("Crack!!!");
    }
}

and the output is

Crack!!!
class java.lang.String

Makes sense now? Object is the supreme super class. So it can get a String object due to polymorphism . Though you are type casting or rather i will say making an integer reference point to a string object Java know internally it is a String object at runtime. There would have been a problem if toString() was not defined in Integer class. The function you call must be defined in the reference but the implementation will be picked up appropriately at runtime from the referenced object.

If you do it this way, you don't even need to check the ClassCastException and it won't even be able to compile.

public class TestGeneric<T> {
    public void get (T arg){
        System.out.println(arg.toString());
    } 
}

TestGeneric<Integer> tg = new TestGeneric<Integer>();
tg.get("Crack!!!");

The type Integer has a method toString . In fact every Object has this method so ClassCastException will not occur.

You did not call any String -specific method on your object thus no exception occured.

The reason for this is that at runtime you will not see the type parameters beause of type erasure .

The point is that after your code is compiled you are no longer to be able to see the generic type parameters because they are erased.

There is another question here which explains class cast exception: explanation

In that code you can see that the user tried to cast explicitly to String not to a generic parameter.

So you can call this a shortcoming of java compared to C#.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM