使用Java中的泛型键入安全性

Type safety with generics in Java

我遇到Java中的泛型行为,我完全不能理解(用.NET背景)。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
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!!!");

请告诉我为什么我没有在GeT中得到ClassCastException,而且,在概念上,我在分配后将temp视为String,并具有"Crack!!!"的值。另外,我怎么能让课堂上的例外会有人投票呢?我在Windows7x64上使用JDK1.7.0_07。


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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
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!!!


这是因为泛型类型t没有定义的边界,因此它被视为对象。在这种情况下,向T投射物体不会导致ClassCastException

但是,如果您的类定义是public class TestGeneric,那么如果您将一个字符串传入get()中,您将得到ClassCastException。


思考通用代码"擦除为"的非通用代码是什么样子是一个有指导意义的练习:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
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?


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

1
2
3
4
5
6
7
8
public class TestGeneric<T> {
    public void get (T arg){
        System.out.println(arg.toString());
    }
}

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

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
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!!!");
    }
}

输出是

1
2
Crack!!!
class java.lang.String

现在有意义了吗?对象是最高的超类。因此,由于多态性,它可以得到一个字符串对象。虽然您是类型转换,但我会说,在Java内部知道一个字符串对象的整数引用点,它在运行时是一个字符串对象。如果未在integer类中定义toString(),则会出现问题。您调用的函数必须在引用中定义,但在运行时将适当地从引用的对象中获取实现。


这是由于type-erasure造成的。这意味着在Java中,所有泛型在运行时都被还原为EDCOX1×1。所以你把你的String投给了Object,这是完全好的。由于toStringObject上实施,也不例外。

这里有一个关于类型删除的链接

真正得到ClassCastException的唯一方法是将Class的一个实例传递给泛型类型,然后执行myClass.isInstance(arg)并在false时抛出异常。


类型Integer具有方法toString。事实上,每个Object都有这种方法,因此ClassCastException不会发生。

您没有对对象调用任何特定于String的方法,因此没有发生异常。

原因是在运行时,由于类型擦除,您将看不到类型参数。

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

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

在该代码中,您可以看到用户试图显式地强制转换到String,而不是通用参数。

因此,你可以把它比作C语言的Java缺点。