【问题标题】:Different treatment of ? in extends and super不同的处理?在扩展和超级
【发布时间】:2020-10-22 15:59:24
【问题描述】:

我希望可以使用任何类型来代替通配符 (?)。

extends 确实如此(如我所料),但 super 编译错误(我不明白为什么它与 extends 示例不同)。

我应该如何看待这种差异 - 这是一个错误还是一个功能?

class Why {
    
    void fSuper(List<? super Map<String,?>> lst) {  }
    
    void fExtends(List<? extends Map<String,?>> lst) {  }

    void test(){
        
          // Why any type instead of ? gives error        
          fSuper(new ArrayList<Map<String, String>>()); // compile ERR - WHY ???
          fSuper(new ArrayList<Map<String, Byte>>());  // c. ERR - WHY ???
          fSuper(new ArrayList<Map<String, Map<Short, Boolean>>>()); // c. ERR - WHY ???         
          fSuper(new ArrayList<Map<String,?>>()); // OK
        
          // Any type instead of ? can be used OK   
          fExtends(new ArrayList<Map<String, String>>()); // OK
          fExtends(new ArrayList<Map<String, Byte>>());  // OK
          fExtends(new ArrayList<Map<String, Map<Short, Boolean>>>()); // OK     
          fExtends(new ArrayList<Map<String,?>>()); // OK

这个问题的灵感来自 pdem here 的回答示例

他的例子似乎和我的 IDE 很不一致

void populateList(List<? super Map<String,?>> list) {
    list.clear();
    Map<String, String>  map;
    map = new HashMap<String,String>();
    map.put("key", "value"); // compiles
    // Map<String, String> for List<? super Map<String,?>>
    list.add(map);      // compiles !!
}

附注我了解 PECS(生产者扩展/消费者超级)。(请不要因此而关闭问题)。但我没有看到如何在我的第一个代码 sn-p 中使用它。我想我完全理解第二个代码 sn-p。但我不明白为什么ArrayList&lt;Map&lt;String, String&gt;&gt; 没有在我的第一个代码 sn-p 中编译(使用 ...?super)并且在第二个代码 sn-p 中编译并在我的第一个代码 sn-p (with ...? extends)。

我也意识到使用通配符 (?) 通常会带来麻烦(尤其是在违反 PECS 规定的情况下)。

但是我想深入理解泛型,第一个代码sn-p到目前为止比我理解的要好。

【问题讨论】:

标签: java generics


【解决方案1】:

根据@Nathan Hughes 建议的帖子,我找到了this excellent article

具有下限的通配符实例是泛型类型的所有实例的超类型,其中类型参数是下限的超类型。

在当前情况下,对于List&lt;? super &lt;Map&lt;String, ?&gt;&gt;,我们只能分配类型参数为超类型Map&lt;String, ?&gt;的类型。

一般而言,对于下界通配符,我们应该能够反向分配类型参数(即,在语句中,LHS 中的类型参数必须可分配给 RHS 中的类型参数)。

在下面的代码中,我们不能将前 3 个值分配给类型参数。因此,也不能将具有此类型参数的子类型分配给列表。

HashMap m = new HashMap();
Map<String, String> m1 = (Map<String, ?>)(m); //error
Map<String, Byte> m2 = (Map<String, ?>)(m); //error
Map<String, Map<Short, Boolean>> m3 = (Map<String, ?>)(m); //error
Map<String, ?> m4 = (Map<String, ?>)(m); // ok

【讨论】:

【解决方案2】:

这并不容易,也不短;但本质上并没有那么复杂。

您首先需要了解的是泛型是不变的,您可以阅读更多here 或更长的阅读here 和我最近的答案here。简单来说:

Animal a = ...
Dog d = ...
a = d;

分配a = d 有效,因为Dog 是一个 Animal;但同时:

List<Animal> a = new ArrayList<>();
List<Dog> d = new ArrayList<>();
a = d; 

这是行不通的,因为泛型是不变的(上面的链接解释了为什么会这样)。

这里最简单的部分是? extends ... 形式的有界类型被称为“使类型协变”,即:

 List<? extends Animal> animals = new ArrayList<>();
 animals = d;

会起作用(与上面的示例相反 a = d)。

如果我们想要一个狗列表怎么办?天真的方法说尝试这样的事情:

 List<List<Dog>> dogs = new ArrayList<>();
 List<List<Animal>> animals = new ArrayList<>();
 animals = dogs;

这将失败,因为我们已经知道List&lt;Animal&gt; 不能分配给List&lt;Dog&gt;(换句话说,List&lt;Dog&gt; 不是List&lt;Animal&gt;子类型)。 ? extends... 怎么样?

 List<List<Dog>> dogs = new ArrayList<>();
 List<? extends List<Animal>> animals = new ArrayList<>();
 animals = dogs;

还是没有。要使类型可分配,您需要:

 List<? extends List<? extends Animal>> animals = new ArrayList<>();
 animals = dogs; // now it works

您也可以通过以下方式使其工作:

 List<? extends List<?>> animals = new ArrayList<>();

因为?是所有类型的超类型。


我们需要这个相当长的介绍,因为您也在处理 嵌套 泛型。您使用fExtends(...) 显示的所有示例都可以解释,因为:

  • 有界类型 (? extends...) 使类型协变(可赋值)

  • Map&lt;String, String&gt;Map&lt;String, Byte&gt;Map&lt;String, Map&lt;Short, Boolean&gt;&gt;Map&lt;String, ?&gt;Map&lt;String,?&gt;所有子类型(或自身)。

例如,所有这些都将编译:

 Map<String,?> one = new HashMap<>();
 Map<String, String> two = new HashMap<>();
 Map<String, Byte> three = new HashMap<>();
 // ... and so on for all the cases you have
 one = two;
 one = three;

fSuper 的示例在某种程度上涉及更多,但并没有那么复杂。让我们首先看一下它采用的参数:List&lt;? super Map&lt;String,?&gt;&gt;:未知类型X 的列表,其中X 必须是Map&lt;String,?&gt;超类型(或自身)。这就是为什么:

fSuper(new ArrayList<Map<String, ?>>());

有效:因为你传递了一个 self 类型的参数:Map&lt;String, ?&gt;(和ArrayList is-a List)。我认为这对您来说很容易掌握。

这个:

fSuper(new ArrayList<Map<String, Map<Short, Boolean>>>())

无法编译,因为您需要传递Map&lt;String,?&gt;超类型,这是这种情况吗?让我们举例说明:

method-argument:    Map < String,            ?          >
passed-value   :    Map < String,  Map<Short, Boolean>  >

Map&lt;Short, Boolean&gt;?超类型。不,它是一个子类型,因此它失败了。

在同一门课程中:

  • Byte?超类型

  • String?超类型

这也是这些方法失败的原因。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2010-12-26
    • 1970-01-01
    • 1970-01-01
    • 2016-04-15
    • 2012-10-19
    • 2012-04-15
    相关资源
    最近更新 更多