我遇到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!!! |
- 你真的"需要"还是有更好的"最佳实践"?我喜欢前面提到的Nitegazer2003方法,,因为它是明确声明的/可见的/简洁的,不会使代码体混乱。它也与C中如何限制泛型非常相似。
- 虽然你已经正确回答了他的问题,但是有一个问题是如何正确地使用泛型,为什么你需要一个"返回"在什么都没有结束?看起来比需要的更复杂。我已经添加了关于如何使用泛型的答案。
这是因为泛型类型t没有定义的边界,因此它被视为对象。在这种情况下,向T投射物体不会导致ClassCastException。
但是,如果您的类定义是public class TestGeneric,那么如果您将一个字符串传入get()中,您将得到ClassCastException。
- +1回答OP关于如何实际获得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,这是完全好的。由于toString在Object上实施,也不例外。
这里有一个关于类型删除的链接
真正得到ClassCastException的唯一方法是将Class的一个实例传递给泛型类型,然后执行myClass.isInstance(arg)并在false时抛出异常。
- 这真的是"唯一的方法"吗?Nitegazer2003提到了一个更清晰的方法,它扩展了number>,因为它清楚地声明了/可见/简洁,并且不会使代码体混乱。但是对于"Java中所有泛型都被简化为对象"的1。
- 我所说的"唯一方法"是,泛型在运行时没有定义的类型。因此,如果所需的行为是抛出ClassCastException,则必须通过传入Class的实例显式地将该类型提供给泛型,然后该实例可用于检查该类型。你对Number的评论只有在符合条件的情况下才有效。通用实现中没有任何建议它只适用于Number的实例的内容。
类型Integer具有方法toString。事实上,每个Object都有这种方法,因此ClassCastException不会发生。
您没有对对象调用任何特定于String的方法,因此没有发生异常。
原因是在运行时,由于类型擦除,您将看不到类型参数。
关键是,在编译代码之后,您将无法再看到泛型类型参数,因为它们已被清除。
这里还有一个解释类强制转换异常的问题:解释
在该代码中,您可以看到用户试图显式地强制转换到String,而不是通用参数。
因此,你可以把它比作C语言的Java缺点。
- -1:当你投射东西时,不会自动调用toString,无论如何,这个投射是另一个方向。
- 我没有说它是自动呼叫的。
- 编辑后,你的答案似乎根本没有提到字符串,这是适当的,因为它是无关的。如果我真的投了反对票,我现在就收回它,但看起来我的反对票没有接受。
- "您没有在对象上调用任何特定于字符串的方法",但实际上,不可能在该引用上调用任何特定于字符串的方法,因为它的类型为T,类型参数可以是任何引用类型。