我经历了这些话题
但是,我似乎仍然对super
关键字感到失望:
当我们宣布这样的集合时:
List<? super Number> list = null;
list.add(new Integer(0));//this compiles
list.add(new Object());//this doesn't compile
不应该相反 - 我们有一个列表,其中包含一些Number
父项的对象(未知类型)。因此,Object
应该适合(因为它是Number
的父级),Integer
不应该。static void test(List<? super Number> param) {
param.add(new Integer(2));
}
public static void main(String[] args) {
ArrayList<String> sList = new ArrayList<String>();
test(sList); //will never compile, however...
}
。情况恰恰相反。
如果我们有以下代码
String is Object, Object is superclass of Number. So String should work.
编译上面的代码是不可能的(我的理智表明这是正确的行为),但基本逻辑可能证明相反:
<S super T>
我知道这很疯狂,但这不是他们不允许<? super T>
构造的原因吗?如果是,那么为什么允许{{1}}?
有人可以帮我恢复这条逻辑链的缺失部分吗?
答案 0 :(得分:92)
List<? super Number>
中的有界通配符可以捕获Number
及其任何超类型。自Number extends Object implements Serializable
起,这意味着List<? super Number>
目前只能抓取可转换的类型是:
List<Number>
List<Object>
List<Serializable>
请注意,您可以add(Integer.valueOf(0))
使用上述任何类型。但是,您不能 add(new Object())
到List<Number>
或List<Serializable>
,因为这违反了通用类型安全规则。
因此, NOT 是真的,您可add
Number
到List<? super Number>
的任何超类型;这根本不是限制通配符和捕获转换的工作方式。您没有声明List<? super Number>
,因为您可能想要向其添加Object
(您不能!);你这样做是因为你想向它添加Number
个对象(即它是Number
的“消费者”),而List<Number>
仅限于extends
。
super
,消费者 - new Integer(0)
valueOf
vs {{1}}等答案 1 :(得分:20)
第一部分List<Number>
适合List<? super Number>
,但您无法将Object
添加到List<Number>
。这就是为什么您无法将Object
添加到List<? super Number>
。
另一方面,您可以将Number
(包括Number
)的每个子类添加到列表中。
对于第二部分,String
是Object
,但String
不是Number
的超类。
如果它像这样工作,因为每个类都是Object
的子类,super
没有任何意义。
List<? super Number>
的所有可能案例:List<Object>
List<Object>
可以使用 Object
符合<? super Number>
Number
的任何子类型添加到List<Object>
String
,您唯一可以确定的是可以添加Number
的任何子类。List<Number>
:
List<Number>
可以使用 Number
符合<? super Number>
Number
的任何子类型添加到List<Number>
List<Integer>
(或Number
的任何子类):
List<Integer>
无效 Number
的子类,因此它正是我们想要避免的内容Integer
适合Number
,您也无法在Number
中添加List<Integer>
的任何子类(例如Float
})super
并不代表子类。List<String>
(或任何未扩展Number
的类,也不在Number
的“超级层次结构”中(即{{1} }和Number
):
Object
无效 List<String>
不适合String
“超级层次结构”Number
符合String
(Object
的超类),您也无法确定能够Number
添加Number
{1}}包含来自List
)Number
并不意味着其中一个超类的任何子类,它只表示其中一个超类。它是如何运作的?
您可以说,只要您可以使用键入的super
添加Number
的任何子类,它就会尊重List
关键字。
答案 2 :(得分:4)
我有一段时间没有得到它。这里的许多答案以及其他问题都具体说明了某些用法何时何地出错,而不是原因。
这就是我最终得到它的方式。如果我有一个将Number
添加到List
的函数,我可能想要添加MySuperEfficientNumber
类型的函数,这是我自己的实现Number
的自定义类(但是不是Integer
的子类。现在调用者可能对MySuperEfficientNumber
一无所知,但只要他们知道将添加到列表中的元素视为比Number
更具体,他们就可以了。
如果我将我的方法声明为:
public static void addNumbersToList(List<? extends Number> numbers)
然后来电者可以传入List<Integer>
。如果我的方法在MySuperEfficientNumber
的末尾添加了numbers
,则调用者将不再拥有List
Integer
个,并且以下代码将无法正常工作:
List<Integer> numbers = new ArrayList<Integer>();
addNumbersToList(numbers);
// The following would return a MySuperEfficientNumber not an Integer
Integer i = numbers.get(numbers.size()-1)
显然这不起作用。错误将在addNumbersToList
方法内。你会得到类似的东西:
The method add... is not applicable for the arguments (MySuperEfficientNumber)
因为numbers
可以是任何特定类型的Number
,但不一定是MySuperEfficientNumber
兼容的内容。如果我将声明翻转为使用super
,则该方法将编译时没有错误,但调用者的代码将失败:
The method addNumbersToList(List<? super Number>)... is not applicable for the arguments (List<Integer>)
因为我的方法是说,&#34;不要认为你的List
可能比Number
更具体。我可能会在列表中添加各种奇怪的Number
,您只需处理它。如果您想将它们视为比Number
更为通用的内容 - 例如Object
- 那很好,我保证它们至少会Number
s,但如果你愿意,你可以更普遍地对待它们。&#34;
而extends
正在说,&#34;我并不关心你给我的List
是什么,只要每个元素至少是Number
。它可以是任何类型的Number
,甚至是您自己的奇怪,自定义,组成Number
。只要他们实现了这个界面,我们就会很好。我不会在您的列表中添加任何内容,因为我不知道您在那里使用的实际具体类型。&#34;
答案 3 :(得分:3)
List<? super Number>
表示变量的引用类型表明我们有一个Numbers,Objects或Serializables列表。
您无法添加Object的原因是因为编译器不知道这些类的WHICH在实际实例化对象的泛型定义中,因此它只允许您传递Number的数字或子类型,像Double,Integer等。
假设我们有一个返回List<? super Number>
的方法。在方法中创建对象是从我们的视图中封装的,我们只是不能说它是否是这样的:
List<? super Number> returnValue = new LinkedList<Object>();
或
List<? super Number> returnValue = new ArrayList<Number>();
因此,泛型类型可以是Object或Number。在这两种情况下,我们都可以添加Number,但只有在一种情况下我们才允许添加Object。
在这种情况下,您必须区分引用类型和实际对象类型。
答案 4 :(得分:2)
List<? super Number>
就是List<AncestorOfNumber>
,我们可以将每个Number
隐含地投射到其超级类型AncestorOfNumber
。
请考虑以下示例:以下示例中需要哪种泛型类型????
?
InputStream mystream = ...;
void addTo(List<????> lsb) {
lsb.add(new BufferedInputStream(mystream));
}
List<BufferedInputStream> lb = new ArrayList<>();
List<InputStream> li = new ArrayList<>();
List<Object> lo = new ArrayList<>();
...
{ addTo(lb); addTo(li); addTo(lo); }
答案:????
是我们可以投射BufferedInputStream
的任何东西,它是与祖先相同或相同的一个:? super BufferedInputStream
答案 5 :(得分:1)
我可以举一个非常简单的例子。
public void add(List<? super Number> list) {
}
此将允许这些呼叫
add(new LinkedList<Number>());
以及以上所有内容类似
add(new LinkedList<Object>());
但没有层次结构下的任何内容,因此不
add(new LinkedList<Double>());
或
add(new LinkedList<Integer>());
因此,由于尚不清楚程序是否知道您给出带有Number或Object的列表,因此编译器无法允许您在Number之上添加任何内容。
例如,列表将不接受对象,尽管对象将接受数字。但是由于不清楚,唯一有效的输入将是Number及其子类型。
答案 6 :(得分:1)
这里有两个角度:涉及边界类型时,您可以放入什么以及可以从集合中获得什么。
首先让我们看一下? extends Number
的情况。当定义了具有这样边界的集合时,我们知道的是:每个元素的上限都为Number
。我们不知道确切的类型(可能是Integer/Long/etc
),但是我们确实知道它的上限是Number
。
因此,从这样的集合中阅读会给我们一个Number
。这是我们可以从中获得的唯一保证类型。
禁止向这样的收藏写东西。但为什么?我不是在阅读的时候说过-我们总是会得到Number
,所以为什么禁止写呢?这里涉及的情况稍微多一些:
List<Integer> ints = ....;
List<? extends Number> numbers = ints;
numbers.add(12D); // add a double in here
如果允许在numbers
中添加,则可以在Double
中有效地添加List of Integers
。
现在来看您的示例:
List<? super Number> list = null;
list.add(new Integer(0));
list.add(new Object());
我们知道list
,其中包含Number
的某个超类型,例如Object
。
可以为我们提供某种类型的X
,其中X
是Number
的父级。那会是什么呢?你真的不知道它可以是理论上的MyNumber extends Number
,也可以简单得多:Object
。由于您不能确定,因此唯一可以读取的安全信息是所有内容的超类型-Object
。
有点奇怪的是:
List<? super String> list = ...;
String s = list.get(0); // fails, compiler does not care that String is final
写作稍微复杂一点,但只是一点点。记住我们知道的list
内部内容:Number
是扩展/实现(如果是接口)的类型,因此您始终可以分配子类型(或Number
本身)作为该超型。
Some type X
/ \
|
Number
/ \
|
Some type Y that we an put in here