List <Map <String,String >>与List <?扩展Map <String,String >>


129

之间有什么区别

List<Map<String, String>>

List<? extends Map<String, String>>

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


2
我喜欢Java,但这不是很好的事情之一
Mite Mitreski 2012年

4
我觉得,如果我们像读“扩展了……的任何东西”那样读它,那就很清楚了。
2012年

令人难以置信的是,在大约3天内,观看次数超过12000?
Enu.Fouad 2012年

5
它到达了《黑客新闻》的首页。恭喜。
r3st0r3 2012年

1
@Eng。发现这里。不再在首页上,而是在昨天。news.ycombinator.net/item?id=3751901(昨天是印度这里的周日中旬)
r3st0r3 2012年

Answers:


180

区别在于,例如

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
}

你可能会认为一个ListHashMapS的关系是一个ListMapS,但有一个很好的理由,为什么它不是:

假设您可以执行以下操作:

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)

所以这就是为什么ListHashMap不是应该是一个ListMap秒。


6
仍然HashMapMap由于多态性所致。
Enu.Fouad 2012年

46
对,但是ListHashMaps不是ListMap秒。
真实性2012年


好的例子。另外值得注意的是,即使您声明List<Map<String,String>> maps = hashMaps; HashMap<String,String> aMap = new HashMap<String, String>();,您仍然会发现maps.add(aMap);合法的同时这是非法的hashMaps.add(aMap);。目的是防止添加错误的类型,但不允许添加正确的类型(编译器在编译时无法确定“正确”的类型)
Raze 2012年

@Raze不,实际上,您可以将s 添加HashMapMaps 的列表中,如果我正确阅读它们,那么您的两个示例都是合法的。
真实性2012年

24

您不能将诸如类型的表达式分配List<NavigableMap<String,String>>给第一个。

(如果你想知道为什么你不能分配List<String>List<Object>看到数不胜数上的其他问题。)


1
能进一步解释吗?我听不懂或没有任何良好实践的联系?
萨米尔·曼格罗里亚

3
@Samir解释什么?List<String>不是的子类型List<Object>?-例如,请参见stackoverflow.com/questions/3246137/…–
汤姆·哈特芬

2
这不能解释有或没有之间的区别? extends。它也没有解释与上级/子类型或协方差/协方差的相关性(如果有)。
亚伯2012年

16

我在其他答案中所缺少的是一般情况下,尤其是与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<Car> 不会继承List<Automobile>,因此无法彼此分配。仅泛型类型参数具有继承关系。有人可能会认为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());

您甚至可以使用比较器来调用它,该比较器比较对象并以任何类型使用它。

何时使用对数或协方差?

也许有些时间,您没有问,但这有助于理解您的问题。一般来说,当你得到某物时,请使用协方差;而当您放置某物时,请使用逆方差。可以在对Stack Overflow问题的答案中最好地解释这一点。在Java泛型中将如何使用逆变?

那那是什么 List<? extends Map<String, String>>

您使用extends,因此适用协方差规则。在这里,您有一个地图列表,并且列表中存储的每个项目都必须是Map<string, string>或从中派生。该声明List<Map<String, String>>不能获得Map,但必须是一个 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>());

“那么……到底是什么”中的任何内容都不会编译。
NobleUplift

@NobleUplift:如果不提供错误消息,很难帮助您。另外,也可以考虑对SO提出新的问题,以获得更大的成功机会。上面的代码只是片段,它取决于您在场景中实现的情况。
亚伯

我没有新问题,有改进。List<? extends Map<String, String>> mapList = new ArrayList<? extends Map<String, String>>(); mapList.add(new TreeMap<String, String>());结果found: ? extends java.util.Map<java.lang.String,java.lang.String> required: class or interface without boundsList<Map<String, String>> mapList = new ArrayList<Map<String, String>>(); mapList.add(new TreeMap<String, String>());完美地工作。最后一个例子显然是正确的。
NobleUplift 2013年

@NobleUplift:很抱歉,长时间旅行,将在我回来时修复,并感谢您指出明显的错误!:)
Abel 2013年

没问题,我很高兴它将得到解决。如果您要批准,我会为您进行修改。
NobleUplift 2013年

4

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

我有一个方法可以接受我最初使用此签名编写SetA对象:

void myMethod(Set<A> set)

但是它实际上想使用Set的子类的调用它A。但这是不允许的!(这样做的原因是,myMethod可以向中添加set类型为的对象A,但不能为set对象声明为在调用者站点。因此,如果可能的话,这可能会破坏类型系统。)

现在是泛型的急救,因为如果我改用此方法签名,它将按预期工作:

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

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

void myMethod(Set<? extends A> set)

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


0

如您所述,定义列表可能有以下两种版本:

  1. List<? extends Map<String, String>>
  2. List<?>

2很开放。它可以容纳任何对象类型。如果您想拥有给定类型的地图,这可能没有用。例如,如果有人不小心放置了其他类型的地图,Map<String, int>。您的使用者方法可能会中断。

为了确保List可以容纳给定类型的对象,引入了Java泛型? extends。因此,在#1中,List可以容纳从Map<String, String>类型派生的任何对象。添加任何其他类型的数据将引发异常。

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.