列表<map <string,string =“”>&gt; vs List <! - ?扩展Map <String,String - >&gt; </map <string,>

时间:2012-03-21 18:15:20

标签: java generics inheritance polymorphism

之间有什么区别吗?
List<Map<String, String>>

List<? extends Map<String, String>>

如果没有差异,使用? extends有什么好处?

5 个答案:

答案 0 :(得分:175)

不同之处在于,例如,

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
}

你会认为List HashMap的{​​{1}}应该是List Map,但有充分的理由不这样做:

假设您可以这样做:

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)

因此,List HashMap的{​​{1}}不应该是List Map的原因。

答案 1 :(得分:25)

您无法将包含List<NavigableMap<String,String>>等类型的表达式分配给第一个。

(如果您想知道为什么不能将List<String>分配给List<Object>,请查看关于SO的 zillion 其他问题。)

答案 2 :(得分: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());

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

何时使用对抗或共同变异?

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

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

是什么呢

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

答案 3 :(得分:4)

今天,我已经使用了这个功能,所以这是我非常新鲜的现实生活中的例子。 (我已经将类和方法名称更改为通用名称,因此它们不会分散实际观点。)

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

void myMethod(Set<A> set)

但它实际上想用Set的{​​{1}}子类调用它。但这是不允许的! (原因是,A可以向myMethod添加set类型的对象,但不能添加A的对象被声明为的子类型调用者的网站。如果可能的话,这可能会破坏类型系统。)

现在这里有一些拯救的泛型,因为如果我使用这种方法签名,它会按预期工作:

set

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

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

这样,void myMethod(Set<? extends A> set) 的类型成为set实际子类型的对象集合,因此可以将其与子类一起使用而不会危及类型系统。

答案 4 :(得分:0)

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

  1. List<? extends Map<String, String>>
  2. List<?>
  3. 2非常开放。它可以容纳任何对象类型。如果您想要一个给定类型的地图,这可能没用。如果有人意外地放置了不同类型的地图,例如Map<String, int>。您的消费者方法可能会中断。

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