这是为了保持与旧版本的Collection
(未泛型化)的向后兼容性吗?还是我遗漏了更微妙的细节?我在remove(Object o)
中也看到了这种模式重复,但add
则使用了泛型化的add(E e)
。
这是为了保持与旧版本的Collection
(未泛型化)的向后兼容性吗?还是我遗漏了更微妙的细节?我在remove(Object o)
中也看到了这种模式重复,但add
则使用了泛型化的add(E e)
。
contains()
方法接收一个Object类型的参数,因为它所匹配的对象不必与传递给contains()
的参数对象具有相同的类型;只需要它们相等即可。根据contains()
方法的规范,如果存在一个对象e
满足(o==null ? e==null : o.equals(e))
,则contains(o)
返回true。请注意,没有什么要求o
和e
必须具有相同的类型。这是由于equals()
方法接收一个Object类型的参数,而不仅仅是与该对象具有相同类型的参数。
虽然许多类通常在equals()
中定义了其对象只能等于其自身类的对象,但这并不总是正确的。例如,List.equals()
规范指出,如果两个List
对象是不同的List实现,但内容相同,它们仍然相等。 因此回到本问题的示例,可以有一个Collection<ArrayList>
并且我可以使用LinkedList
作为参数调用contains()
,如果有相同内容的列表,则可能返回true。如果contains()
是泛型并限制其参数类型为E
,则这将是不可能的。
实际上,contains()
方法接受任何对象作为参数的事实允许一种有趣的用法,即您可以使用它来测试集合中是否存在满足某个属性的对象:
Collection<Integer> integers;
boolean oddNumberExists = integers.contains(new Object() {
public boolean equals(Object e) {
Integer i = (Integer)e;
if (i % 2 != 0) return true;
else return false;
}
});
contains( Object o )
实际上是一个很好用的自我毁灭工具。这个方法展现了Java泛型的局限性。 - Alexander Pogrebnyakcontains()
是通用的并将其参数类型限制为E
,那么这是不可能的。但是,如果contains
有一个重载,接受一个Comparator<E>
,那么仍然可以实现相同的目标 - 这样就可以以您建议的方式(使用任意谓词和条件执行相等比较)进行操作,并以更安全的方式进行操作。 - Dai在这里回答:
为什么Java集合的删除方法不是泛型的?
简而言之,他们想要最大化向后兼容性,因为集合在泛型引入之前就已经存在了。
我想补充一点:他所提到的视频值得一看。
http://www.youtube.com/watch?v=wDN_EYUvUq0
更新
澄清一下,那个人(在视频中)是将Java映射和集合更新为使用泛型的人之一。如果他不知道,那么谁知道呢。
contains
函数利用了equals
函数,而equals
函数在基础Object类中定义,签名为equals(Object o)
而不是equals(E e)
(因为不是所有类都是泛型)。remove
函数也是同样的情况 - 它使用equals
函数遍历集合,该函数接受一个对象参数。
这并没有直接解释这个决定,因为他们仍然可以使用类型E并允许在调用equals
时自动转换为类型Object;但我想他们希望允许该函数在其他对象类型上调用。有一个Collection<Foo> c;
,然后调用c.contains(somethingOfTypeBar)
是没有问题的 - 它总是返回false,因此它消除了对类型Foo的转换的需要(可能会抛出异常),或者为了保护异常,需要使用typeof
调用。所以你可以想象如果你正在迭代混合类型的内容,并在每个元素上调用contains
,你可以简单地在所有元素上使用contains函数,而不需要使用保护措施。contains
和remove
上进行一定程度的类型检查会更好,这样就会减少错误的发生。 - Alexander Pogrebnyakclass Base
{
}
class Derived
extends Base
{
}
Collection< ? extends Base > c = ...;
Derived d = ...;
Base base_ref = d;
c.contains( d ); // Would have produced compile error
c.contains( base_ref ); // Would have produced compile error
编辑
对于那些认为这不是原因的怀疑者,这里有一个修改过的数组列表,带有可能泛型化的包含方法。
class MyCollection< E > extends ArrayList< E >
{
public boolean myContains( E e )
{
return false;
}
}
MyCollecttion< ? extends Base > c2 = ...;
c2.myContains( d ); // does not compile
c2.myContains( base_ref ); // does not compile
基本上contains( Object o )
是一种hack,用于使这个非常常见的用例与Java Generics一起使用。
void myMethod( Collection<? extends Foo> coll )
。如果你需要在其中查看是否包含了某个版本的Foo
,那么你将会遇到这个答案中描述的限制。 - Alexander Pogrebnyak“那个苹果篮子里有这个橙吗?”
显然是无法给出正确答案的,但依然有两种可能:
集合 API 选择了第一种方式。但第二种选择也是完全有道理的。像这样的问题在99.99%的情况下都是废话,所以干脆别问!
Fruit
的方法应该也可用于Orange
。在 .Net 中有一些方法可以使用Obsolete
标记来触发编译器警告,在某些特定的静态可识别的傻瓜情况下,但我认为这样做可能会更加混乱而不是有帮助。 - supercatE
声明类型变量。这不会违反Liskov原则,因为泛型方法是重载方法。 - irreputable