Java Generics:通配符捕获误解

时间:2012-08-20 19:28:52

标签: java generics wildcard

阅读Java在线教程我对通配符捕获一无所知。 例如:

    import java.util.List;
    public class WildcardError {
     void foo(List<?> i) {
      i.set(0, i.get(0));
     }
    }

为什么编译器无法保留赋值安全? 它知道,通过执行例如带有整数列表的方法,它从i.get得到一个Integer值。因此,它尝试将索引0处的Integer值设置为相同的整数列表(i)。 那么,出了什么问题?为什么要编写通配符助手?

6 个答案:

答案 0 :(得分:21)

  

为什么编译器无法保留赋值安全?它知道,通过执行例如带有整数列表的方法,它从i.get得到一个Integer值。因此,它尝试将索引0处的Integer值设置为相同的整数列表(i)。

换句话说,为什么编译器不知道

中通配符类型List<?>的两种用法
i.set(0, i.get(0));

指的是相同的实际类型?

那么,这将要求编译器知道i包含表达式的两个评估的相同实例。由于i甚至不是最终的,因此编译器必须检查在评估两个表达式之间是否可能已分配i。这样的分析只对局部变量很简单(谁知道被调用的方法是否会更新特定对象的特定字段?)。这在编译器中非常复杂,因为它很少显示出好处。我想这就是为什么Java编程语言的设计者通过指定相同通配符类型的不同用法具有不同的捕获来保持简单。

答案 1 :(得分:19)

  

为什么编译器无法保持赋值安全?

根据List<?> i的定义,编译器不知道{{1>}中{em>任何有关元素类型的内容。通配符表示“任何类型”;它的意思是“某种未知的类型。”

  

它知道,通过执行例如带有整数列表的方法,它从i.get得到一个整数值。

这是真的,但正如我上面所说:编译器只能 知道 - 在编译时,请记住 - ?返回i.get(0) },这是Object的上限。但是不能保证? 在运行时 ?,因此编译器无法知道Object是安全呼叫。这就像写这个:

i.set(0, i.get(0))

更多阅读:

答案 2 :(得分:1)

我也觉得这个问题难以理解;将单个命令分成两个命令帮助了我。

下面的代码是在检查和编译原始方法时后台实际发生的情况,编译器创建自己的局部变量:i.get(0)调用的结果放在局部变量堆栈的寄存器中。

那就是 - 为了理解这个问题 - 就像制作一个局部变量一样,为方便起见,我给它起了名字element

import java.util.List;
public class WildcardError {
 void foo(List<?> i) {
  Object element = i.get(0);  // command 1
  i.set(0, element);          // command 2
 }
}

当检查命令1时,它只能将element的类型设置为Object( - &gt;上限概念,请参阅Matt的答案),因为它无法使用?作为变量类型; ?仅用于表示通用类型未知。

变量类型只能是实数类型或泛型类型,但由于您在此方法中不使用泛型类型,例如<T>,因此强制使用实数类型。由于java规范中的以下行(jls8,18.2.1):

,因此执行此强制
  

形式的约束公式如下:

     

[...]

     

- 如果表达式是类实例创建表达式或方法调用表达式,则约束将缩减为绑定集B3,用于在定位T时确定表达式的调用类型,如第18.5.2节中所定义。 (对于类实例创建表达式,用于推理的相应“方法”在第15.9.3节中定义)。

答案 3 :(得分:1)

解决方案将会

vector (m_resultVect)

这里fooHelper将捕获通配符的类型T吗? (例如通配符捕获名称)。

答案 4 :(得分:0)

根据Get-Put原理:

  1. 如果您像List<? extends Something>中那样扩展了通配符,则:

    1A。您可以使用Something或它的superclass引用从结构中获取。

    void foo(List<? extends Number> nums) {
       Number number = nums.get(0); 
       Object number = nums.get(0); // superclass reference also works.
    }
    

    1B。您不能在结构中添加任何内容(null除外)。

    void foo(List<? extends Number> nums) {
       nums.add(1); Compile error
       nums.add(1L); Compile error
       nums.add(null); // only null is allowed.
    }
    
  2. 类似地,如果您具有List<? super Something>中的超级通配符,则:

    2A。您可以添加到Something或其subclass结构中。 例如:

    void foo(List<? super Number> nums) {
        nums.add(1); // Integer is a subclass of Number
        nums.add(1L); // Long is a subclass of Number
        nums.add("str"); // Compile error: String is not subclass of Number         
    }
    

    2A。您无法从结构中获取(通过对象引用除外)。 例如:

    void foo(List<? super Integer> nums) {
        Integer num = nums.get(0); // Compile error
        Number num = nums.get(0); // Compile error
        Object num = nums.get(0); // Only get via Object reference is allowed.        
    }
    

回到OP的问题,List<?> i只是List<? extends Object> i的简称。并且由于它是extends通配符,所以set操作失败。

剩下的最后一块是为什么操作失败?还是为什么首先采用“获取-放置”原则? -这与Jon Skeet here回答的类型安全有关。

答案 5 :(得分:0)

我想您对限制的误解是由于将?替换为any typeObject或您的想法。但是这种假设是不正确的,?实际上意味着unknown type。所以在下一行

fooz.set(0, foo);

您正在尝试将某种类型的变量分配给未知类型的变量(因为函数签名就像void set(int, ?)一样),无论foo的类型如何,这都是不可能的是。在您的情况下,foo的类型为Object,并且您不能将其分配给某个未知类型的变量,实际上它可以是FooBar或其他任何变量。