我今天遇到了Java泛型的一些奇怪行为。以下代码编译正常并按预期工作:
import java.util.*;
public class TestGeneric
{
public static void main(String[] args)
{
GenericClass<Integer> generic = new GenericClass<Integer>(7);
String stringFromList = generic.getStringList().get(0);
}
static class GenericClass<A>
{
private A objA;
private List<String> stringList;
GenericClass(A objA)
{
this.objA = objA;
stringList = new ArrayList<String>();
stringList.add("A string");
stringList.add("Another string");
}
A getObjA()
{
return objA;
}
List<String> getStringList()
{
return stringList;
}
}
}
但是如果你将变量泛型的类型更改为GenericClass(注意没有类型参数),则编译失败并显示消息&#34;不兼容的类型:java.lang.Object无法转换为java.lang.String&#34;
这个问题似乎发生在包含具有具体类型参数的泛型对象的任何泛型类中。一些谷歌搜索没有发现任何东西,JLS没有提及这种情况?我做错了什么,或者这是javac中的错误?
答案 0 :(得分:4)
原始类型GenericClass
被视为已擦除,包括类未声明的泛型类型。因此getStringList
会返回原始List
而不是参数化List<String>
。
我发现很难在Java规范中找到一件事来指出这一点,但这是正常行为。
这是另一个例子。
public class Test {
public static void main(String[] args) {
String s;
// this compiles
s = new Generic<Object>().get();
// so does this
s = new Generic<Object>().<String>get();
// this doesn't compile
s = new Generic().get();
// neither does this
s = new Generic().<String>get();
}
}
class Generic<A> {
<B> B get() { return null; }
}
A
和B
都被删除,但B
由方法声明。这很好奇。
这种惊人的细微差别就是为什么要警告不要使用原始类型。
答案 1 :(得分:3)
跟进@ Radiodef的回答,我可能已经找到了JLS语言:
来自§15.12.2.6(SE8):
最具体的可访问和适用的调用类型 method是表示目标类型的方法类型(第8.2节) 调用参数,结果(返回类型或void) 调用,以及调用的异常类型。它是 确定如下:
这意味着一旦编译器确定了它调用的方法,它就会确定统称为方法类型的几个属性。其中一个属性是返回类型,它成为表达式的类型(方法调用);这是我们唯一对此感兴趣的人。
使用原始示例,其中方法(getStringList
)不是通用的:
如果所选方法不是通用的,那么:
如果要采用未经检查的转换方法, 调用类型的参数类型是参数类型 方法的类型,和返回类型以及抛出类型由下式给出 返回类型的删除和方法类型的抛出类型。 [强调我的]
“未经检查的转换”表示将原始类型转换为参数化类型(第5.1.9节)。我找不到“[a]方法适用时”未经检查的转换时间的具体定义“。但是在object.method
形式中,确定适用性的第一步是确定要搜索的类或接口(第15.12.1节),这是object
的类,可能发生的是由于generic
(在generic.getStringList()
中)的原始类型为GenericClass
,而我们需要搜索的类为GenericClass<A>
,因此这相当于未经检查的转换,方法适用。不过,我并不是100%肯定我是对的。
但是假设我得到了这个权利,这意味着上面的段落适用,因此调用的返回类型是方法类型的返回类型的擦除。因此,generic.getStringList()
的类型为List
,而不是List<String>
,正如@Radiodef所指出的那样。编写规则的方式,从List<String>
中删除的类型参数与原始类型中缺少的类型参数无关;收紧规则以在需要删除时删除类型,但在这种情况下不会,可能会非常困难。
因此,一旦返回类型被视为List
而不是List<String>
,那么除非将其转换为List<String>
或分配给声明为List<String>
的内容,否则它将被视为List
,get
来电的返回类型为Object
。
我引用的规则是非通用方法。对于泛型方法,如在@ Radiodef的示例中,适用的实际规则是在我引用的段落之上的几个段落,但它对返回类型的描述与非泛型方法的相同。