使用通配符创建新的泛型对象

13
请解释一下这个通用代码中的通配符编译时错误:
//no compile time error.
List<? extends Number> x = new ArrayList<>(); 

//compile time error.  
List<? extends Number> x = new ArrayList<? extends Number>();

2
第一个例子显然只适用于Java 7,而不适用于Java 6,因为在Java 6中不允许使用可选的钻石。第二个例子在两种情况下都失败了。 - Sam Adamsh
1
第一个肯定在6上不起作用;没有钻石操作符。至于其余部分,您可能需要检查此资源:http://www.angelikalanger.com/GenericsFAQ/JavaGenericsFAQ.html。 - BillRobertson42
2个回答

27

使用通配符实例化泛型类型是无效语法。类型List<? extends Number>表示一个List,其中包含一些类型,这些类型是Number或其子类。创建此类型的实例没有意义,因为实例化会创建特定的内容:

new ArrayList<? extends Number>();//compiler:"Wait, what am I creating exactly?" 

通配符泛型只有在变量和方法参数中才有意义,因为这允许更大的自由度来分配/传递它们。

//compiler:"Okay, so passing in a List<Integer> or a List<Double> are both fine"
public void eatSomeNumbers(List<? extends Number> numbers) {
    for (Number number : numbers) {
        System.out.println("om nom " + number + " nom");
    }
}

请记住,在使用通配符时存在的限制。

List<? extends Number> numList = ...
numList.add(new Integer(3));//compiler:"Nope, cause that might be a List<Double>"

关于你的第一个例子,钻石语法是Java 7中的新功能,它允许编译器根据分配给它的变量类型来推断新泛型实例的类型。在这种情况下:

List<? extends Number> x = new ArrayList<>();

编译器大概是在这里推断出了 new ArrayList<Number>(),但推断出来的并不重要,只要它是给定变量的有效赋值即可。这就是引入菱形操作符的原因 - 指定新对象的泛型类型是多余的,只要任何泛型类型都可以使其成为有效的赋值/参数。

这种推理只有在您记得 Java 中的泛型是仅在编译时存在的语言特性,由于 类型擦除,在运行时没有意义。通配符只存在于此限制之下。相比之下,在 C# 中,泛型类型信息会保留到运行时 - 该语言中不存在泛型通配符。


在实例化匿名类时,有没有绕过这个限制的方法? - shmosel
@shmosel 我不这么认为,但是我不确定使用 new Foo<?>() { }new Foo<Object>() { } 相比有什么好处。你能详细解释一下你的使用情况吗? - Paul Bellora

1

使用

 List<? extends Number> x = new ArrayList<Number>();

改用。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接