之间有什么区别
List<Map<String, String>>
和
List<? extends Map<String, String>>
?
如果没有区别,使用的好处是? extends
什么?
之间有什么区别
List<Map<String, String>>
和
List<? extends Map<String, String>>
?
如果没有区别,使用的好处是? extends
什么?
Answers:
区别在于,例如
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
S的关系是一个List
的Map
S,但有一个很好的理由,为什么它不是:
假设您可以执行以下操作:
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
不是应该是一个List
的Map
秒。
HashMap
是Map
由于多态性所致。
List
的HashMap
s不是List
的Map
秒。
List<Map<String,String>> maps = hashMaps;
和HashMap<String,String> aMap = new HashMap<String, String>();
,您仍然会发现maps.add(aMap);
合法的同时这是非法的hashMaps.add(aMap);
。目的是防止添加错误的类型,但不允许添加正确的类型(编译器在编译时无法确定“正确”的类型)
HashMap
到Map
s 的列表中,如果我正确阅读它们,那么您的两个示例都是合法的。
我在其他答案中所缺少的是一般情况下,尤其是与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());
您甚至可以使用比较器来调用它,该比较器比较对象并以任何类型使用它。
也许有些时间,您没有问,但这有助于理解您的问题。一般来说,当你得到某物时,请使用协方差;而当您放置某物时,请使用逆方差。可以在对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>());
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 bounds
。List<Map<String, String>> mapList = new ArrayList<Map<String, String>>(); mapList.add(new TreeMap<String, String>());
完美地工作。最后一个例子显然是正确的。
今天,我已经使用了此功能,所以这是我非常新鲜的真实示例。(我将类和方法名称更改为通用名称,因此它们不会分散实际含义。)
我有一个方法可以接受我最初使用此签名编写Set
的A
对象:
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
,因此可以在不损害类型系统的情况下将其与子类一起使用。
如您所述,定义列表可能有以下两种版本:
List<? extends Map<String, String>>
List<?>
2很开放。它可以容纳任何对象类型。如果您想拥有给定类型的地图,这可能没有用。例如,如果有人不小心放置了其他类型的地图,Map<String, int>
。您的使用者方法可能会中断。
为了确保List
可以容纳给定类型的对象,引入了Java泛型? extends
。因此,在#1中,List
可以容纳从Map<String, String>
类型派生的任何对象。添加任何其他类型的数据将引发异常。