10

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.

4

7 回答 7

16

您没有收到类转换异常的原因是 Java 泛型是通过类型擦除实现的。与需要对 CLS 进行重大更改的 .NET 泛型不同,Java 泛型完全在编译时处理。在运行时,转换T为被忽略。为了在运行时检查类型,您需要存储Class<T>,并使用它的方法来检查传入的参数的类型:

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!!!
于 2013-05-22T10:35:13.803 回答
8

这是因为泛型类型 T 没有定义的边界,因此它被视为对象。ClassCastException在这种情况下,将某些内容投射到 T 不会导致 a 。

但是,如果您的类定义是public class TestGeneric<T extends Number>,那么如果您将 String 传递给 get(),您将得到 ClassCastException。

于 2013-05-22T10:36:02.863 回答
7

思考通用代码被“擦除”到的非通用代码是什么样的,这是一个有启发性的练习:

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?
于 2013-05-22T22:24:16.713 回答
4

永远记住 Java 中的泛型是编译时实体。它与运行时无关。让我给你演示一下你的 oqn 代码。

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!!!");
    }
}

输出是

Crack!!!
class java.lang.String

现在有意义吗?Object是至高无上的超类。所以由于多态性,它可以得到一个 String 对象。尽管您是类型转换,或者更确切地说,我会说创建一个指向字符串对象的整数引用点 Java 在内部知道它是运行时的 String 对象。如果toString()没有在 Integer 类中定义,就会出现问题。您调用的函数必须在引用中定义,但实现将在运行时引用的对象中适当地提取。

于 2013-05-22T10:42:51.773 回答
4

如果你这样做,你甚至不需要检查 ClassCastException,它甚至无法编译。

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

TestGeneric<Integer> tg = new TestGeneric<Integer>();
tg.get("Crack!!!");
于 2016-03-06T00:17:27.373 回答
3

这是由于type-erasure. 这意味着在 Java 中,所有泛型都减少到Object运行时。所以你已经投了你的StringObject这完全没问题。既然toString是在 上实现的Object,同样也不例外。

这是关于类型擦除的链接

真正获得的唯一方法ClassCastException是将实例传递Class<T>给泛型类型,然后执行myClass.isInstance(arg)并抛出异常,如果false

于 2013-05-22T10:33:34.523 回答
2

该类型Integer有一个方法toString。事实上每个Object人都有这种方法所以ClassCastException不会发生。

您没有String在对象上调用任何 -specific 方法,因此没有发生异常。

这样做的原因是在运行时您将看不到类型参数,因为类型擦除

关键是在编译代码后,您将无法再看到泛型类型参数,因为它们已被删除。

这里还有另一个问题解释了类强制转换异常:解释

在该代码中,您可以看到用户尝试显式转换为String不转换为通用参数。

因此,您可以将其称为 java 与 C# 相比的缺点。

于 2013-05-22T10:35:18.610 回答