考虑这段代码:
public class Main {
public static void main(String[] args) {
Cat<Integer> cat = new Cat();
Integer i= cat.meow();
cat.var = 6;
}
}
public class Cat<E> {
public E var;
public E meow() {
return null;
}
}
根据我的理解,因为我没有在LHS上指定类型参数,所以它将被视为Object
。并且Cat
应该变为Cat<Object>
因为变量声明有任何意义T必须转换为类/接口引用。这是对它如何运作的正确理解吗? 如果是原始类型,如何处理类型参数?
我已经在聊天中讨论了这个问题并获得了following解释,这是我的想法:
泛型有效,因为类型被删除。考虑一下擦除 #0捕获的-对象。如果没有指定T(rawtyped),那么#0-capture-of-(nothing)
#0-capture-of-(nothing)
是什么意思?
附注:,因为泛型类型是作为编译时转换实现的,如果可以看到最终翻译的代码,则更容易理解它们。有没有人知道一种方法来查看类的翻译代码(而不是字节代码)?
答案 0 :(得分:2)
原始类型不像它们是Object
的参数,也不像通配符类型(<?>
)。
对于原始类型,泛型已关闭。
此代码编译(带警告):
Cat c1 = new Cat<String>();
Cat<Integer> c2 = c1;
此代码不会:
Cat<? extends Object> c1 = new Cat<String>(); // btw: this is the same as Cat<?>
Cat<Integer> c2 = c1; // error
这两个都没有:
Cat<Object> c1 = new Cat();
Cat<Integer> c2 = c1; // error
var
:编译后字段的类型是参数的上限(Object
,如果没有指定)。但是,如果我们访问var
,编译器会做什么?
Cat<String> c1 = ...
String c1Var = c1.var;
此代码编译时没有错误,但编译器实际编译的是:
Cat c1 = ...
String c1Var = (String) c1.var;
如您所见,var
始终被视为Object
类型的字段,但带有泛型,编译器会自动插入类型安全的强制转换。 这就是。如果你使用原始类型,你必须自己做。无论哪种方式,当您将Cat
存储在Cat<String>
变量中的整数时,如果您尝试阅读var
,则只会获得运行时异常。
现在看看Collections.max的声明。为什么您认为参数定义为T extends Object & Comparable<? super T>
?
以rot13编码的答案:
Fb gung nsgre pbzcvyngvba gur erghea glcr vf
Bowrpg
,abgPbzcnenoyr
。 Guvf vf arrqrq sbe onpxjneqf pbzcngvovyvgl(gur zrgubq vgfrys vf byqre guna trarevpf)。
这是我偶然发现的另一个很好的例子:
class Foo<T> {
public <V> V bar(V v) { return v; }
}
//compiles
Foo<Object> foo = new Foo<Object>();
Integer i = foo.bar(1);
//compiles
Foo<?> foo = new Foo<String>();
Integer i = foo.bar(1);
// fails
Foo foo = new Foo();
Integer i = foo.bar(1); // error: Object cannot be converted to Integer
不使用参数会完全禁用泛型。
答案 1 :(得分:1)
此代码有效:
Cat c = new Cat<Integer>();
c
现在属于原始类型Cat
。
这是无效的:
Cat<Object> c = new Cat<Integer>(); // Compiler error
所以,它并不完全相同。虽然你可以在第一行之后做一些事情:
c.var = 5;
System.out.println(c.var);
c.var = 1;
System.out.println(c.var);
c.var = "test";
System.out.println(c.var);
输出:
5
1
test
答案 2 :(得分:1)
@Cephalopod
已经提供了正确的答案,但是我想通过我自己的一些解释来扩展它。
使变量声明有意义T必须转换为类/接口引用。
这是正确的。泛型是编译时转换。运行时系统没有抽象类型的概念。因此,在将类加载到内存之前,抽象类型T必须替换为实际的类型引用。
运行以下代码:
System.out.println(Cat.class.getMethod("meow").getReturnType());
System.out.println(Cat.class.getField("var").getType());
输出结果为:
class java.lang.Object
class java.lang.Object
正式类型参数E
已替换为Object
。
Cat
应该成为Cat<Object>
错误。 Cat
将保留Cat
。为什么?查看Main
的反编译类文件:
public class Main {
public static void main(String[] args) {
Cat cat = new Cat();
Integer i = (Integer)cat.meow();
cat.var = Integer.valueOf(6);
}
}
使用<>
指定形式类型参数的目的是使编译器能够生成显式转换。
当你说new Cat()
它不必变成任何东西时,编译器只是不会生成强制转换,方法调用看起来像:
Integer i = cat.meow(); // No cast at all
泛型类型参数是否已转换为原始类型的对象?
为了澄清此处的问题,上述问题意味着:如果我在实例化E
时没有指定任何内容,java.lang.Object
会被Cat
替换。
实际上即使您在实例化E
时指定了java.lang.Object
,<Integer>
也会被Cat
替换。替换/转换在编译时完成,而实例化在运行时。如何使用该类型不会改变其类定义。
答案 3 :(得分:0)
我没有指向这方面的链接,但我宁愿假设c
变为原始类型Cat
,而不是Cat<Object>
。
原始类型不处理参数T,这就是它们容易出错的原因。
javadoc说:原始类型是没有任何类型参数的泛型类或接口的名称。
这个聊天记录似乎就是这个意思,但是却让人感到困惑。
答案 4 :(得分:0)
我实际上不知道它是如何在字节码中实际实现的但是根据我的理解Cat c = new Cat<Integer>();
在变量Cat
中存储new Cat<Integer>()
创建的c
的新实例。现在,如果您查询c
以了解var
的类型,它将回复Integer
而不是Object
,因为创建的实例的类型为{{1} }。
现在如果您执行Integer
并查询c以了解c.var = "Text";
的类型。它会回复var
。这并不意味着默认情况下它会将String
转换为<T>
。这意味着Object
不知道c
的类型是什么。
我觉得这就是使用var
通配符的原因。 <?>
它始终会将Cat<?> c = new Cat<Integer>();
转换为<T>
。这就是为什么总是建议不要使用泛型的原始类型。
答案 5 :(得分:0)
我认为Cat c是一种RAW类型,可以被视为像Cat<?>
这样的“通配符类型”。由于Cat<?>
是包含Cat<Integer>
的每种类型Cat的超类型,Cat c
可能会占用new Cat<Integer>
个对象。
这里也提到了这一点:Interoperating with Legacy Code
“大多数人的第一直觉是Collection真正意味着Collection。但是,正如我们之前看到的那样,在需要Collection的地方传递Collection是不安全的。更准确地说类型Collection表示一些未知类型的集合,就像Collection一样。“
...
“所以原始类型非常类似于通配符类型,但它们并没有严格限制。这是一个深思熟虑的设计决策,允许泛型与预先存在的遗留代码进行互操作。”