两者有什么区别吗
和
1
List
<? extends Map
< String , String
>>
?
如果没有区别,使用? extends 有什么好处?
我喜欢Java,但这是一件不那么好的事情…
我觉得如果我们把它读成"任何延伸的东西…",它就会变得清晰。
难以置信,在3天内浏览量超过12K?!!!
它登上了黑客新闻的头版。恭喜.
@你能给我那个页面的链接吗?:)
@工程师在这里找到的。不再出现在头版,而是昨天出现的。news.ycombinator.net/item?ID=3751901(昨天是印度的星期日中旬。)
区别在于,例如,
是一个
1
List
<? extends Map
< String ,String
>>
但不是
所以:
1 2 3 4 5 6 7 8 9
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
}
你会认为HashMap s的List 应该是Map s的List ,但这是有充分理由的:
假设你能做到:
1 2 3 4 5 6 7 8 9 10 11 12
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 。
对,但HashMap s的List 不是Map s的List 。
我懂了。现在,很明显:)。非常感谢+1。
这叫做有界量化
很好的例子。同样值得注意的是,即使你宣布List> maps = hashMaps; 和HashMap aMap = new HashMap(); ,你仍然会发现maps.add(aMap); 是非法的,而hashMaps.add(aMap); 是合法的。目的是防止添加错误的类型,但不允许添加正确的类型(编译器在编译期间无法确定"正确"类型)
@Raze不,事实上你可以在Map 的列表中添加一个HashMap ,如果我正确阅读的话,你的两个例子都是合法的。
@真实性,哎呀!我是说江户十一〔十五〕号!!!!
@拉泽啊,是的。我想记住的一般规则是,您不添加到 extends ...> 集合中,也不从 super ...> 集合中读取。
@Trutheality剩下28张选票,以获得"伟大答案"徽章;)
@真谛恭喜你获得金质答题徽章:)
@谢谢你!没有你是不可能做到的。
不能将类型为List> 的表达式赋给第一个。
(如果你想知道为什么你不能把List 分配给List ,请看无数其他问题。)
能进一步解释吗?我不能理解或任何良好实践的链接?
@萨米尔解释什么?List 不是List 的亚型吗?-例如,请参见stackoverflow.com/questions/3246137/&hellip;
好的,谢谢你的帮助。…+1好的答案
这不能解释有或没有? extends 之间的区别。也不能解释与超/亚型或协/反方差(如果有)的相关性。
我在其他答案中所缺少的是一个参考,它涉及到共同和逆变、亚和超类型(即多态性),特别是Java。这一点可以被操作人员很好地理解,但以防万一,它是这样的:
协方差
如果您有一个类Automobile ,那么Car 和Truck 是它们的子类型。任何汽车都可以被分配给汽车类型的变量,这在OO中是众所周知的,被称为多态性。协方差是指在使用泛型或委托的场景中使用相同的原则。Java没有委托(还),所以这个术语只适用于泛型。
我倾向于将协方差视为标准多态性,即您不需要思考就可以工作的东西,因为:
1 2 3 4
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 不是从List 继承的,因此不能互相分配。只有泛型类型参数具有继承关系。有人可能会认为Java编译器不够聪明,无法正确地理解你的脚本。但是,您可以通过提示编译器来帮助它:
1 2
List< Car> cars;
List<? extends Automobile> automobiles = cars; // no error
逆变
共同方差的反义是反向方差。在协方差中,参数类型必须具有子类型关系,反之,它们必须具有父类型关系。这可以被视为继承上限:允许任何父类型向上,包括指定的类型:
1 2 3 4
class AutoColorComparer implements Comparator< Automobile>
public int compare( Automobile a, Automobile b) {
// Return comparison of colors
}
这可用于collections.sort:
1 2 3 4 5
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
( ) ) ;
甚至可以使用比较对象并将其用于任何类型的比较器来调用它。
何时使用抵销或共同方差?
也许有点不对劲,你没有问,但这有助于理解回答你的问题。一般来说,当你得到某物时,使用协方差,当你放入某物时,使用逆方差。在堆栈溢出问题的答案中,这是最好的解释:逆变换在Java泛型中如何使用?.
那么,用
List extends Map> 是什么呢?
您使用extends ,因此协方差规则适用。这里有一个地图列表,您存储在列表中的每个项目必须是Map 或从中派生。声明List> 不能从Map 派生,但必须是Map 。
因此,由于TreeMap 继承了Map 的遗产,因此以下内容将起作用:
1 2
List
< Map
< String , String
>> mapList
= new ArrayList
< Map
< String , String
>> ( ) ;
mapList.
add ( new TreeMap
< String , String
> ( ) ) ;
但这不会:
1 2
List
<? extends Map
< String , String
>> mapList
= new ArrayList
<? extends Map
< String , String
>> ( ) ;
mapList.
add ( new TreeMap
< String , String
> ( ) ) ;
这也不会起作用,因为它不满足协方差约束:
1 2
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 关键字只适用于该参数,而不适用于其他参数。即,以下内容不会编译:
1 2
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等存储在映射中,那么您可以执行如下操作:
1 2 3 4 5
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:如果你没有提供你得到的错误信息,很难帮助你。另外,作为一种选择,也可以考虑问一个新的问题,以便得到你的答案,更大的成功机会。上面的代码只是片段,这取决于您在场景中实现了它。
我没有新问题,我有改进。List extends Map> mapList = new ArrayList extends Map>(); mapList.add(new TreeMap()); 产生found: ? extends java.util.Map required: class or interface without bounds 。List> mapList = new ArrayList>(); mapList.add(new TreeMap()); 工作得很好。最后一个例子显然是正确的。
@诺布列普里夫特:对不起,长途旅行,我回来后会修好的,谢谢你指出了明显的错误!:)
没问题,我很高兴能修好。如果你想批准的话,我为你做了编辑。
今天,我已经使用了这个特性,所以这里是我最新的实际例子。(我已将类和方法名称更改为通用名称,这样它们就不会偏离实际点。)
我有一种方法可以接受我最初用这个签名写的A 对象的Set :
1
void myMethod( Set< A> set)
但它想用A 的子类Set s来称呼它。但这是不允许的!(原因是,myMethod 可以向Set 添加类型为A 的对象,但不属于Set 的对象被声明在调用方站点的子类型。因此,如果可能的话,这可能会破坏类型系统。)
现在,我们来拯救泛型,因为如果我使用此方法签名,它将按预期工作:
1
< T extends A> void myMethod( Set< T> set)
或者更短,如果不需要在方法体中使用实际类型:
1
void myMethod( Set<? extends A> set)
这样,Set 的类型就变成了A 的实际子类型对象的集合,因此可以在不危及类型系统的情况下将其与子类一起使用。
正如您所提到的,定义列表可能有以下两个版本:
List extends Map>
List>
2非常开放。它可以容纳任何对象类型。如果您想要一个给定类型的映射,这可能不太有用。如果有人意外地放置了不同类型的地图,例如,Map 。您的消费者方法可能会中断。
为了确保EDCOX1〔3〕能够保存给定类型的对象,Java泛型引入EDCOX1×15。因此,在1中,List 可以容纳来自Map 类型的任何对象。添加任何其他类型的数据都会引发异常。