【问题标题】:Java generics super keywordJava泛型超级关键字
【发布时间】:2010-10-02 19:15:04
【问题描述】:

我浏览了这些主题

但是,我似乎仍然对 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.

我知道这很疯狂,但这不是他们不允许&lt;S super T&gt; 构造的原因吗?如果是,那么为什么允许&lt;? super T&gt;?

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

【问题讨论】:

    标签: java generics


    【解决方案1】:

    List&lt;? super Number&gt; 中的有界通配符可以捕获 Number 及其任何超类型。由于Number extends Object implements Serializable,这意味着当前唯一可以被List&lt;? super Number&gt;捕获转换的类型是:

    • List&lt;Number&gt;
    • List&lt;Object&gt;
    • List&lt;Serializable&gt;

    请注意,您可以add(Integer.valueOf(0)) 到上述任何类型。但是,您不能将add(new Object()) 转换为List&lt;Number&gt; 或List&lt;Serializable&gt;,因为这违反了泛型类型安全规则。

    因此不您可以将add 的任何超类型Number 转换为List&lt;? super Number&gt;;这根本不是有界通配符和捕获转换的工作方式。您没有声明List&lt;? super Number&gt;,因为您可能想向其中添加Object(您不能!);你这样做是因为你想向它添加Number 对象(即它是Number 的“消费者”),而简单的List&lt;Number&gt; 限制性太强。

    参考文献

    另见

    • Effective Java 2nd Edition,第 28 条:使用有界通配符提高 API 灵活性
      • "PECS 代表生产者-extends,消费者-super

    相关问题

    • 太多了,无法列出,PECS、new Integer(0) 与 valueOf 等

    【讨论】:

    • 我无法确认您的答案是对还是错,因为它太令人困惑并且假定了太多知识。如果我们能理解这样的答案,就不会问这个问题!
    • @AlexWorden 这个答案在技术上是非常正确的,毫无疑问,如果你想要一个轻量级的解释,可能是this can help
    【解决方案2】:

    对于第一部分List&lt;Number&gt; 适合List&lt;? super Number&gt;,但您不能将Object 添加到List&lt;Number&gt;。这就是为什么您不能将Object 添加到List&lt;? super Number&gt;。

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

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

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


    让我们看看List&lt;? super Number&gt; 的所有可能情况:


    • 通过的列表是List&lt;Object&gt;
      • List&lt;Object&gt; 可以使用
      • Object 适合 &lt;? super Number&gt;
      • 您可以将Number 的任何子类型添加到List&lt;Object&gt;
      • 即使您也可以在其中添加String,但您唯一确定的是您可以添加Number 的任何子类。

    • 通过的列表是 List&lt;Number&gt; :
      • List&lt;Number&gt; 可以工作
      • Number 适合 &lt;? super Number&gt;
      • 您可以将Number 的任何子类型添加到List&lt;Number&gt;

    • 传递的列表是一个List&lt;Integer&gt;(或Number的任何子类):
      • List&lt;Integer&gt; 不起作用
      • Integer 是 Number 的子类,所以它正是我们想要避免的
      • 即使Integer 适合Number,您也无法在List&lt;Integer&gt; 中添加Number 的任何子类(例如Float)
      • super 不代表子类。

    • 通过的列表是 List&lt;String&gt;(或任何不扩展 Number 也不在 Number 的“超级层次结构”中的类(即 Number 和 Object):
      • List&lt;String&gt; 不起作用
      • String 不适合 Number“超级等级”
      • 即使String 适合Object(这是Number 的超类),您也不一定能将Number 添加到包含任何子类的List Number 的超类)
      • super 并不表示超类之一的任何子类,它仅表示超类之一。

    它是如何工作的?

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

    【讨论】:

    • @Vuntic,泛型很难完全清楚,但我更新了我的答案并尝试了:)
    • 感谢科林,这让我更清楚了。这个话题需要自己试验一下才能完全理解
    • 这是解释类属的好方法,给出了所有可能的场景,但你可以添加一些颜色和粗体格式,使事情更清晰易读;)
    【解决方案3】:

    我有一段时间没有得到它。这里的许多答案和其他问题都具体说明了某些用法在何时何地是错误的,但并没有说明原因。

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

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

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

    然后调用者可以传入List&lt;Integer&gt;。如果我的方法在numbers 的末尾添加了MySuperEfficientNumber,那么调用者将不再有Integers 的List,并且以下代码将不起作用:

    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>)
    

    因为我的方法是说,“不要认为你的List 可以比Number 更具体。我可能会在列表中添加各种奇怪的Numbers,你只会有处理它。如果你想把它们看作比Number 更一般的东西——比如Object——没关系,我保证它们至少是Numbers,但你可以对待它们如果您愿意,可以更一般地使用。”

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

    【讨论】:

      【解决方案4】:

      List&lt;? super Number&gt; 表示变量的引用类型表明我们有一个数字、对象或可序列化的列表。

      不能添加 Object 的原因是因为编译器不知道这些类中的哪些在实际实例化对象的通用定义中,所以它只允许你传递 Number 或 Number 的子类型,如 Double , 整数等。

      假设我们有一个返回List&lt;? super Number&gt; 的方法。方法内部对象的创建是从我们的角度来封装的,只是不能说是这样的:

      List&lt;? super Number&gt; returnValue = new LinkedList&lt;Object&gt;();

      或

      List&lt;? super Number&gt; returnValue = new ArrayList&lt;Number&gt;();

      因此,泛型类型可以是 Object 或 Number。在这两种情况下,我们都可以添加 Number,但只有在一种情况下,我们才可以添加 Object。

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

      【讨论】:

        【解决方案5】:

        List&lt;? super Number&gt; 就是这样一个List&lt;AncestorOfNumber&gt;,我们可以将每个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

        【讨论】:

          【解决方案6】:

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

          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 的 List,编译器不允许您向其添加 Number 以上的任何内容。

          例如,List 不会接受 Object,尽管 Object 会接受 Number。但由于不清楚,唯一有效的输入是 Number 及其子类型。

          【讨论】:

            【解决方案7】:

            这里有两个角度:当涉及到有界类型时,您可以放入集合中的内容以及可以从集合中获取的内容。


            我们先来看? 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,您可以有效地在List of Integers 中添加Double。


            现在来看你的例子:

             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
            

            【讨论】:

              猜你喜欢
              • 2013-04-02
              • 2023-03-23
              • 1970-01-01
              • 2016-09-21
              • 1970-01-01
              • 1970-01-01
              • 2011-02-05
              • 1970-01-01
              • 2018-04-10
              相关资源
              最近更新 更多