Java泛型超级关键字


76

我经历了这些话题

但是,我似乎仍然对super关键字感到迷茫:

  1. 当我们声明这样的集合时:

    List<? super Number> list = null;
    list.add(new Integer(0)); // this compiles
    list.add(new Object()); // this doesn't compile
    

不应该相反吗?我们有一个列表,其中包含一些对象(类型未知),它们是的父对象Number。因此Object应该适合(因为它是的父项Number),Integer而不应该。由于某种原因,情况恰恰相反。

  1. 只要我们有以下代码

    static void test(List<? super Number> param) {
      param.add(new Integer(2));
    }
    
    public static void main(String[] args) {
      List<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>允许?

有人可以帮助我恢复此逻辑链的缺失部分吗?

Answers:


98

中的有界通配符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>,因为这违反了通用类型安全规则。

因此,它是不是真的,你可以add任意超NumberList<? super Number>; 根本不是有限的通配符和捕获转换如何工作。您不声明a是List<? super Number>因为您可能想向其中添加一个Object(您不能!);之所以这样做,是因为您要向其中添加Number对象(即,它是的“消费者” Number),而仅仅是aList<Number>过于严格。

参考文献

也可以看看

  • 有效的Java 2nd Edition,第28项:使用有界通配符可提高API的灵活性
    • “ PECS代表生产者extends,消费者,super

相关问题

  • 列出太多,PECS,new Integer(0)vsvalueOf

16
我无法确定您的答案是对还是错,因为它太令人困惑并且假设了太多知识。如果我们能理解这样的答案,就不会问这个问题!
亚历克斯·沃登

@AlexWorden这个答案从技术上讲是非常正确的,对此毫无疑问,如果您想要一个简短的解释,可能会对您有所帮助
Eugene

22

对于第一部分,List<Number>三天打鱼两天List<? super Number>,但你不能将添加ObjectList<Number>。这就是为什么你不能将添加ObjectList<? super Number>

另一方面,您可以将NumberNumber包括)的每个子类添加到列表中。

对于第二部分,String是的Object,但String不是的超类Number

如果它像这样工作,则因为每个类都是的子类Objectsuper则将没有任何意义。


让我们看一下每种可能的情况List<? super Number>


  • 传递的清单是一个 List<Object>
    • List<Object> 将工作
    • Object 适合 <? super Number>
    • 您可以将的任何子类型添加NumberList<Object>
    • 即使您也可以添加String,您唯一可以确定的是可以添加的任何子类Number

  • 传递的列表是List<Number>
    • List<Number> 将工作
    • Number 适合 <? super Number>
    • 您可以将的任何子类型添加NumberList<Number>

  • 传递的列表是List<Integer>(或的任何子类Number):
    • List<Integer> 不会工作
    • 整数是的子类,Number因此正是我们要避免的
    • 即使Integer在配合Number你不会abble添加的任何子类NumberList<Integer>(例如Float
    • super 并不意味着子类。

  • 传递的列表是List<String>(或任何既不扩展Number也不在Number(例如NumberObject)的“超级层次结构”中的类:
    • List<String> 不会工作
    • String不适合Number“上级”
    • 即使String在配合Object(这是一个超类的Number),你woudln't时一定要能够将添加Number到一个List包含从父类中的一个任何子类Number
    • super 并不表示其中一个超类的任何子类,而仅表示其中一个超类。

它是如何工作的 ?

您可以说,只要您可以Number使用typed添加任何的子类List,它就会尊重super关键字。


1
@Vuntic,使用泛型很难一清二楚,但是我更新了答案,然后尝试了:)
Colin Hebert 2010年

1
谢谢科林,这对我来说更清楚了。本主题需要用它进行试验了一下,充分认识它
丹尼斯Kniazhev

多数民众赞成在解释通用性,给出所有可能的情况的一种好方法,但是您可以添加一些颜色和大胆的格式以使内容更清晰易读;)
Andrzej Rehmann

7

我有一段时间没有得到它。这里的许多答案以及其他问题专门显示某些用法在何时何地是错误的,但原因不多。

这就是我最终得到它的方式。如果我有一个将Numbers添加到的函数,则List可能要添加它们的类型MySuperEfficientNumber,这是我自己实现的自定义类Number(但不是的子类Integer)。现在,调用者可能不了解MySuperEfficientNumber,但是只要知道将添加到列表中的元素视为没有比特定的元素Number,它们就可以了。

如果我将我的方法声明为:

public static void addNumbersToList(List<? extends Number> numbers)

然后,呼叫者可以传入 List<Integer>。如果我的方法MySuperEfficientNumber在的末尾添加了a numbers,则调用方将不再具有的ListInteger并且以下代码将不起作用:

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是与之兼容的东西。如果我将声明翻转为use super,则该方法将编译而不会出错,但是调用者的代码将失败并显示以下内容:

The method addNumbersToList(List<? super Number>)... is not applicable for the arguments (List<Integer>)

因为我的方法是说:“不要以为你的 List可以比...更具体Number。我可以Number在列表中添加各种怪异的东西,您只需要处理它即可。如果您想起它们如一些更普遍比Number-喜欢Object-这很好,我保证他们会至少NumberS,但你可以更一般地,如果你想对待他们“。

extends虽这么说,“我真的不在乎List您给我什么样的东西,只要每个元素至少是一个Number。它可以是任何一种Number,甚至您自己的怪异,习俗,虚构的东西Number。他们实现了该接口,我们很好。我不会在您的列表中添加任何内容,因为我不知道您在那里使用的是什么实际的具体类型。”


4

List<? super Number> 表示变量的引用类型表明我们有一个数字,对象或可序列化的列表。

之所以不能添加对象,是因为编译器不知道这些类的哪个在实际实例化对象的通用定义中,因此它仅允许您传递Number或Number的子类型,例如Double,Integer和以此类推。

假设我们有一个返回的方法List<? super Number>。从我们的观点来看,方法内部对象的创建是封装的,我们不能说是否是这样的:

List<? super Number> returnValue = new LinkedList<Object>();

要么

List<? super Number> returnValue = new ArrayList<Number>();

因此,通用类型可以是对象或数字。在这两种情况下,我们都被允许添加Number,但是仅在一种情况下,我们被允许添加Object。

在这种情况下,您必须区分引用类型和实际对象类型。


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


2

我可以举一个非常简单的例子。

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及其子类型。


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
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.