之间有什么区别吗?
List<Map<String, String>>
和
List<? extends Map<String, String>>
如果没有差异,使用? extends
有什么好处?
答案 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
,那么Car
和Truck
就是他们的子类型。可以将任何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)
今天,我已经使用了这个功能,所以这是我非常新鲜的现实生活中的例子。 (我已经将类和方法名称更改为通用名称,因此它们不会分散实际观点。)
我有一个方法可以接受我最初使用此签名编写的Set
个A
个对象:
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可能有以下两个版本:
List<? extends Map<String, String>>
List<?>
2非常开放。它可以容纳任何对象类型。如果您想要一个给定类型的地图,这可能没用。如果有人意外地放置了不同类型的地图,例如Map<String, int>
。您的消费者方法可能会中断。
为了确保List
能够容纳给定类型的对象,引入了Java泛型? extends
。所以在#1中,List
可以包含从Map<String, String>
类型派生的任何对象。添加任何其他类型的数据都会引发异常。