泛型类型参数是否转换为原始类型的Object?

时间:2015-04-29 11:39:50

标签: java generics

考虑这段代码:

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)是什么意思?

附注:,因为泛型类型是作为编译时转换实现的,如果可以看到最终翻译的代码,则更容易理解它们。有没有人知道一种方法来查看类的翻译代码(而不是字节代码)?

6 个答案:

答案 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,abg Pbzcnenoyr。 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一样。“

...

“所以原始类型非常类似于通配符类型,但它们并没有严格限制。这是一个深思熟虑的设计决策,允许泛型与预先存在的遗留代码进行互操作。”