有什么区别吗?
List<Map<String, String>>
和
List<? extends Map<String, String>>
如果没有区别,那么使用 ? extends
有什么好处呢?
有什么区别吗?
List<Map<String, String>>
和
List<? extends Map<String, String>>
如果没有区别,那么使用 ? extends
有什么好处呢?
List<HashMap<String,String>>
是一个
List<? extends Map<String,String>>
但不是a
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
应该是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)
HashMap
的List
不应该是Map
的List
。HashMap
的列表不是 Map
的列表。 - truthealityList<Map<String,String>> maps = hashMaps;
和HashMap<String,String> aMap = new HashMap<String, String>();
,您仍然会发现maps.add(aMap);
是非法的,而hashMaps.add(aMap);
是合法的。这样做的目的是防止添加错误类型,但它不允许添加正确类型(编译器无法在编译时确定“正确”类型)。 - RazeList<? extends Map<String, String>> maps = ...
!!! - Raze您无法将诸如 List<NavigableMap<String,String>>
等类型的表达式分配给第一个参数。
(如果您想知道为什么不能将 List<String>
分配给 List<Object>
,请参见 Stack Overflow 上无数其他问题。)
List<String>
不是List<Object>
的子类型?” - 例如,参见https://dev59.com/E3A75IYBdhLWcg3wg5js - Tom Hawtin - tackline? extends
有什么区别,也没有解释与超类/子类型或协变/逆变的相关性(如果有的话)。 - Abel如果你有一个类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问题的答案 How would contravariance be used in Java generics?中最好地解释了。
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>());
则完美运行。显然,最后一个例子是正确的。 - NobleUpliftvoid myMethod(Set<A> set)
A
的子类的 Set
来调用它。但这是不允许的!(原因是,myMethod
可以向 set
添加类型为 A
而不是调用方所声明的子类型的对象。如果可能的话,它会破坏类型系统。)现在,泛型来解救了它,因为如果我改用这个方法签名,它就能按预期工作:<T extends A> void myMethod(Set<T> set)
void myMethod(Set<? extends A> set)
set
的类型变成了A
实际子类型的对象的集合,因此可以在不危及类型系统的情况下与子类一起使用。正如您所提到的,定义List有以下两个版本:
List<? extends Map<String, String>>
List<?>
第二种方法非常开放。它可以容纳任何对象类型。但如果您想要一个给定类型的映射,则可能没有用处。例如,如果有人不小心放入了不同类型的映射,例如Map<String,int>
,则您的使用方法可能会出错。
为了确保List
可以容纳给定类型的对象,Java泛型引入了? extends
。因此,在#1中,List
可以容纳从Map<String,String>
类型派生的任何对象。添加任何其他类型的数据都会抛出异常。