List<Map<String, String>> vs List<?扩展地图<字符串,字符串>>

Posted

技术标签:

【中文标题】List<Map<String, String>> vs List<?扩展地图<字符串,字符串>>【英文标题】:List<Map<String, String>> vs List<? extends Map<String, String>> 【发布时间】:2012-04-06 07:34:37 【问题描述】:

有什么区别

List<Map<String, String>>

List<? extends Map<String, String>>

?

如果没有区别,使用? extends有什么好处?

【问题讨论】:

我喜欢 java,但这是不太好的事情之一...... 我觉得如果我们把它读成“任何扩展......”,它就会变得清晰。 难以置信,在大约 3 天内的浏览量超过 12K?!! 它到达了黑客新闻的头版。恭喜。 @Eng. 在这里找到。不再在头版,但昨天在那里。 news.ycombinator.net/item?id=3751901(昨天是印度的周日中午。) 【参考方案1】:

不同之处在于,例如,一个

List<HashMap<String,String>>

是一个

List<? extends Map<String,String>>

但不是

List<Map<String,String>>

所以:

void withWilds( List<? extends Map<String,String>> foo )
void noWilds( List<Map<String,String>> foo )

void main( String[] args )
    List<HashMap<String,String>> myMap;

    withWilds( myMap ); // Works
    noWilds( myMap ); // Compiler error

你会认为HashMaps 中的List 应该是Maps 中的List,但它不是有充分的理由:

假设你可以这样做:

List<HashMap<String,String>> hashMaps = new ArrayList<HashMap<String,String>>();

List<Map<String,String>> maps = hashMaps; // Won't compile,
                                          // but imagine that it could

Map<String,String> aMap = Collections.singletonMap("foo","bar"); // Not a HashMap

maps.add( aMap ); // Perfectly legal (adding a Map to a List of Maps)

// But maps and hashMaps are the same object, so this should be the same as

hashMaps.add( aMap ); // Should be illegal (aMap is not a HashMap)

这就是为什么HashMaps 中的List 不应该是Maps 中的List

【讨论】:

由于多态性,HashMap 仍然是 Map 对,但HashMaps 的List 不是Maps 的List 这叫Bounded quantification 很好的例子。另外值得注意的是,即使你声明了List&lt;Map&lt;String,String&gt;&gt; maps = hashMaps; HashMap&lt;String,String&gt; aMap = new HashMap&lt;String, String&gt;();,你仍然会发现maps.add(aMap);是非法的,而hashMaps.add(aMap);是合法的。目的是防止添加错误的类型,但不允许添加正确的类型(编译器在编译时无法确定“正确”类型) @Raze 不,实际上你可以HashMap 添加到Maps 的列表中,如果我没看错的话,你的两个例子都是合法的.【参考方案2】:

您不能将类型为 List&lt;NavigableMap&lt;String,String&gt;&gt; 的表达式分配给第一个。

(如果您想知道为什么不能将 List&lt;String&gt; 分配给 List&lt;Object&gt;,请参阅关于 SO 的 zillion 个其他问题。)

【讨论】:

能再解释一下吗?我无法理解或任何良好做法的链接? @Samir 解释什么? List&lt;String&gt; 不是 List&lt;Object&gt; 的子类型? - 参见,例如,***.com/questions/3246137/… 这并不能解释使用或不使用? extends 之间的区别。它也没有解释与超/亚型或协/逆变(如果有的话)的相关性。【参考方案3】:

我在其他答案中缺少的是对这与一般的协变和逆变以及子类型和超类型(即多态性)以及特别是 Java 的关系的参考。 OP 可能很好理解这一点,但以防万一,这里是这样的:

协方差

如果你有一个类Automobile,那么CarTruck 是它们的子类型。任何 Car 都可以分配给 Automobile 类型的变量,这在 OO 中是众所周知的,称为多态。协变是指在具有泛型或委托的场景中使用相同的原则。 Java 还没有委托,因此该术语仅适用于泛型。

我倾向于将协方差视为标准多态性,您可以不假思索地工作,因为:

List<Car> cars;
List<Automobile> automobiles = cars;
// You'd expect this to work because Car is-a Automobile, but
// throws inconvertible types compile error.

然而,错误的原因是正确的:List&lt;Car&gt; 继承自List&lt;Automobile&gt;,因此不能相互分配。只有泛型类型参数具有继承关系。有人可能会认为 Java 编译器根本不够聪明,无法正确理解那里的场景。但是,您可以通过给他一个提示来帮助编译器:

List<Car> cars;
List<? extends Automobile> automobiles = cars;   // no error

逆变

协方差的反面是逆变。在协变中,参数类型必须具有子类型关系,而在逆变中,它们必须具有超类型关系。这可以被认为是继承上限:允许任何超类型并包括指定的类型:

class AutoColorComparer implements Comparator<Automobile>
    public int compare(Automobile a, Automobile b) 
        // Return comparison of colors
    

这可以和Collections.sort一起使用:

public static <T> void sort(List<T> list, Comparator<? super T> c)

// Which you can call like this, without errors:
List<Car> cars = getListFromSomewhere();
Collections.sort(cars, new AutoColorComparer());

您甚至可以使用比较对象的比较器调用它,并将其与任何类型一起使用。

何时使用反方差或协方差?

也许有点过时,你没有问,但它有助于理解回答你的问题。一般来说,当你得到东西时,使用协变,当你东西时,使用逆变。这在an answer to Stack Overflow question How would contravariance be used in Java generics? 中得到了最好的解释。

那么List&lt;? extends Map&lt;String, String&gt;&gt; 又是什么呢?

您使用extends,因此适用于协方差的规则。在这里,您有一个地图列表,并且您存储在列表中的每个项目都必须是 Map&lt;string, string&gt; 或派生自它。语句List&lt;Map&lt;String, String&gt;&gt; 不能派生自Map,而必须是a Map

因此,以下将起作用,因为 TreeMap 继承自 Map

List<Map<String, String>> mapList = new ArrayList<Map<String, String>>();
mapList.add(new TreeMap<String, String>());

但这不会:

List<? extends Map<String, String>> mapList = new ArrayList<? extends Map<String, String>>();
mapList.add(new TreeMap<String, String>());

这也不起作用,因为它不满足协方差约束:

List<? extends Map<String, String>> mapList = new ArrayList<? extends Map<String, String>>();
mapList.add(new ArrayList<String>());   // This is NOT allowed, List does not implement Map

还有什么?

这可能很明显,但您可能已经注意到,使用extends 关键字仅适用于该参数,而不适用于其余参数。即,以下内容将无法编译:

List<? extends Map<String, String>> mapList = new List<? extends Map<String, String>>();
mapList.add(new TreeMap<String, Element>())  // This is NOT allowed

假设您想允许映射中的任何类型,将键作为字符串,您可以在每个类型参数上使用extend。即,假设您处理 XML 并且想要将 AttrNode、Element 等存储在地图中,您可以执行以下操作:

List<? extends Map<String, ? extends Node>> listOfMapsOfNodes = new...;

// Now you can do:
listOfMapsOfNodes.add(new TreeMap<Sting, Element>());
listOfMapsOfNodes.add(new TreeMap<Sting, CDATASection>());

【讨论】:

“So what is it then with...”中的任何内容都不会编译。 @NobleUplift:如果您不提供收到的错误消息,则很难为您提供帮助。作为替代方案,还可以考虑在 SO 上提出一个新问题以获得您的答案,从而获得更大的成功机会。上面的代码只是sn-ps,这取决于你在你的场景中实现它。 我没有新问题,我有改进。 List&lt;? extends Map&lt;String, String&gt;&gt; mapList = new ArrayList&lt;? extends Map&lt;String, String&gt;&gt;(); mapList.add(new TreeMap&lt;String, String&gt;()); 导致 found: ? extends java.util.Map&lt;java.lang.String,java.lang.String&gt; required: class or interface without boundsList&lt;Map&lt;String, String&gt;&gt; mapList = new ArrayList&lt;Map&lt;String, String&gt;&gt;(); mapList.add(new TreeMap&lt;String, String&gt;()); 完美运行。最后一个例子显然是正确的。 @NobleUplift:抱歉,旅行了很长时间,我回来后会修复,感谢您指出明显的错误! :) 没问题,我很高兴它会得到修复。如果您想批准,我为您进行了修改。【参考方案4】:

今天,我已经使用了这个功能,所以这是我非常新鲜的真实示例。 (我已将类和方法名称更改为通用名称,这样它们就不会分散实际的注意力。)

我有一个方法旨在接受我最初使用此签名编写的 A 对象中的 Set

void myMethod(Set<A> set)

但它实际上想用A 的子类的Sets 来调用它。但这是不允许的! (原因是,myMethod 可以向set 添加类型为A 的对象,但不是set 的对象被声明为位于调用者站点的子类型。所以这可能会中断如果可能的话,类型系统。)

现在泛型来拯救,因为如果我使用这个方法签名,它会按预期工作:

<T extends A> void myMethod(Set<T> set)

或更短,如果您不需要在方法体中使用实际类型:

void myMethod(Set<? extends A> set)

这样,set 的类型变成了 A 的实际子类型的对象的集合,因此可以在不危及类型系统的情况下将其与子类一起使用。

【讨论】:

【参考方案5】:

正如您所提到的,定义列表可能有以下两个版本:

    List&lt;? extends Map&lt;String, String&gt;&gt; List&lt;?&gt;

2 非常开放。它可以容纳任何对象类型。如果您想拥有给定类型的地图,这可能没有用。万一有人不小心放了不同类型的地图,例如Map&lt;String, int&gt;。您的消费者方法可能会中断。

为了确保List 可以容纳给定类型的对象,Java 泛型引入了? extends。所以在#1 中,List 可以保存从Map&lt;String, String&gt; 类型派生的任何对象。添加任何其他类型的数据都会引发异常。

【讨论】:

以上是关于List<Map<String, String>> vs List<?扩展地图<字符串,字符串>>的主要内容,如果未能解决你的问题,请参考以下文章

_InternalLinkedHashMap<String, dynamic>' 不是类型 'List<Map<dynamic, dynamic>>' 的子类型

如何将 Hashmap 中的值转换为 List<String>

Dart 映射,如何根据其键 id 替换 list<map> 中的特定键值?

如何在 Java 中创建一些变量类型别名

如何将map<string list<>>转换成城map<string,object>

List<Object> 到 Map<String, Map<String,List<Object>>>