Warning: file_get_contents(/data/phpspider/zhask/data//catemap/9/java/350.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
Java泛型:通配符捕获误解_Java_Generics_Wildcard - Fatal编程技术网

Java泛型:通配符捕获误解

Java泛型:通配符捕获误解,java,generics,wildcard,Java,Generics,Wildcard,阅读Java在线教程,我对通配符捕获一无所知。 例如: import java.util.List; public class WildcardError { void foo(List<?> i) { i.set(0, i.get(0)); } } import java.util.List; 公共类通配符错误{ 无效foo(列表一){ i、 set(0,i.get(0)); } } 为什么编译器不能安全地保留赋值? 它知

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

    import java.util.List;
    public class WildcardError {
     void foo(List<?> i) {
      i.set(0, i.get(0));
     }
    }
import java.util.List;
公共类通配符错误{
无效foo(列表一){
i、 set(0,i.get(0));
}
}
为什么编译器不能安全地保留赋值? 它知道,例如,通过执行带有整数列表的方法,它从i.get获得一个整数值。因此,它尝试将索引0处的整数值设置为相同的整数列表(i)。 那么,怎么了?为什么要编写通配符助手

为什么编译器不能安全地保留赋值

根据
的定义,编译器对
列表i
中的元素类型一无所知。通配符并不表示“任何类型”;它表示“某个未知类型”

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

这是真的,但正如我上面所说的:编译器只能在编译时知道
I.get(0)
返回一个
对象,它是
的上限。但是不能保证
在运行时是
对象
,因此编译器无法知道
i.set(0,i.get(0))
是一个安全调用。就像这样写的:

List<Foo> fooz = /* init */;
Object foo = fooz.get(0);
fooz.set(0, foo); // won't compile because foo is an object, not a Foo
List fooz=/*init*/;
对象foo=fooz.get(0);
fooz.set(0,foo);//无法编译,因为foo是对象,而不是foo
更多阅读:
为什么编译器不能安全地保留赋值?它知道,例如,通过执行带有整数列表的方法,它从i.get获得一个整数值。因此,它尝试将索引0处的整数值设置为相同的整数列表(i)

换句话说,为什么编译器不知道通配符类型的两种用法在

i.set(0, i.get(0));
是指相同的实际类型吗


这需要编译器知道
i
对于表达式的两个求值都包含相同的实例。由于
i
甚至不是最终的,编译器必须检查
i
是否可能在计算两个表达式之间赋值。这种分析只对局部变量简单(因为谁知道调用的方法是否会更新特定对象的特定字段?)。这在编译器中增加了相当多的复杂性,因为很少体现出好处。我想这就是为什么Java编程语言的设计者通过指定同一通配符类型的不同用法有不同的捕获来简化事情的原因。

我也发现这个问题很难理解;将单个命令拆分为两个命令对我很有帮助

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

为了理解这个问题,这与生成一个局部变量是一样的,为了方便起见,我给它命名为
元素

import java.util.List;
public class WildcardError {
 void foo(List<?> i) {
  Object element = i.get(0);  // command 1
  i.set(0, element);          // command 2
 }
}
import java.util.List;
公共类通配符错误{
无效foo(列表一){
Object元素=i.get(0);//命令1
i、 set(0,元素);//命令2
}
}
当检查命令1时,它只能将
元素
的类型设置为
对象
(->上限概念,参见Matt的回答),因为它不能将
用作变量类型;
仅用于指示泛型类型未知

变量类型只能是实类型或泛型类型,但由于在该方法中不使用泛型类型(例如
),因此必须使用实类型。之所以执行此强制,是因为java规范(jls8,18.2.1)中有以下行:

形式è表达式的约束公式→ T›减少如下:

[……]

–如果表达式是类实例创建表达式或方法调用表达式,则约束将减少到绑定集B3,该绑定集B3将用于确定以T为目标时表达式的调用类型,如§18.5.2所定义。(对于类实例创建表达式,用于推理的相应“方法”在§15.9.3中定义)

解决办法是,

import java.util.List;

    public class WildcardError {

    private void fooHelper(List<T> i){
         i.set(0, i.get(0));
    }
    public void foo(List<?> i){
         fooHelper(i);
    }
}
import java.util.List;
公共类通配符错误{
私人助理(名单一){
i、 set(0,i.get(0));
}
公共图书馆(名单一){
食物助理(i);;
}
}

这里fooHelper将捕获通配符的类型T?(作为通配符捕获的名称)。

根据Get-Put原则:


  • 如果您在
    列表中扩展了通配符,我想您对限制的误解可能是因为将
    替换为
    任何类型
    对象
    或类似的东西。但这种假设是不正确的,
    实际上意味着
    未知类型。那么在下面的一行中

    fooz.set(0,foo);
    

    您试图将某种类型的变量分配给未知类型的变量(因为函数签名类似于
    void set(int,?)
    ),这是不可能的,无论
    foo
    的类型是什么。在您的情况下,foo的类型是
    Object
    ,您不能将其分配给某个未知类型的变量,实际上可能是
    foo
    Bar
    或任何其他变量。

    可能重复Erm,投票结束问题并提供答案是否有点不一致?它不是重复的(至少不是该问题的重复).我以前见过关于这个确切例子的问题,但在搜索中似乎找不到。可能与@meriton的答案重复更正确。编译器不必知道任何关于
    void foo(List<? extends Number> nums) {
       Number number = nums.get(0); 
       Object number = nums.get(0); // superclass reference also works.
    }
    
    void foo(List<? extends Number> nums) {
       nums.add(1); Compile error
       nums.add(1L); Compile error
       nums.add(null); // only null is allowed.
    }
    
    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         
    }
    
    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.        
    }