11

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.

starteleport
  • 1,231
  • 2
  • 11
  • 21

7 Answers7

17

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!!!
Sergey Kalinichenko
  • 714,442
  • 84
  • 1,110
  • 1,523
  • 1
    Do you really "need to" or is there a better "best practice"? I like the approach nitegazer2003 mentioned, , since that's clearly declared/visible/concise and doesn't clutter the code body. It also closely parallels how generics can be restricted in C#. – Jon Coombs Jun 15 '14 at 17:11
  • although you have answered his question correctly but there is issue of how to use generic properly and why do you need a "return" at the end of nothing? and looks like ways more complicated than need to be. I have added my answer of how you should use generic. – Jaxox Mar 06 '16 at 00:25
8

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().

nitegazer2003
  • 1,193
  • 5
  • 10
7

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?
newacct
  • 119,665
  • 29
  • 163
  • 224
5

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!!!");
Jaxox
  • 960
  • 4
  • 14
  • 25
4

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

John B
  • 32,493
  • 6
  • 77
  • 98
  • Is it really the "only way"? nitegazer2003 mentioned what strikes me as a cleaner approach, , since that's clearly declared/visible/concise and doesn't clutter the code body. But +1 for "in Java all generics are reduced to Object". – Jon Coombs Jun 15 '14 at 17:07
  • What I mean by "the only way" is that a generic does not have the defined type at runtime. Therefore, if the desired behavior is to throw a ClassCastException, the type must be explicitly provided to the generic by passing in an instance of `Class` which can then be used to check the type. Your comment about `Number` is only valid if that is the condition. There is nothing in the generic implementation that would suggest that it should only work for instances of `Number`. – John B Jun 16 '14 at 12:13
4

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.

Aniket Thakur
  • 66,731
  • 38
  • 279
  • 289
2

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#.

Community
  • 1
  • 1
Adam Arold
  • 29,285
  • 22
  • 112
  • 207
  • 1
    -1: `toString` is not automatically called when you cast something, and this cast is the other direction anyway. – Don Roby May 22 '13 at 10:37
  • 1
    I did not say that it is automatically called. – Adam Arold May 22 '13 at 10:49
  • After edits your answer doesn't seem to reference toString at all, which is appropriate, as it's irrelevant. If I had actually downvoted, I'd retract it now, but it appears my downvote didn't take. – Don Roby May 22 '13 at 10:57
  • 1
    "You did not call any String-specific method on your object" But in fact, it is not possible to call any String-specific method on that reference, because it is of type `T`, a type parameter which can be any reference type. – newacct May 22 '13 at 22:25