Java:通用接口和函数

时间:2010-10-22 15:10:02

标签: java generics interface

我仍在尝试Java如何处理泛型。我偶然发现了一个事实/问题/事情,如果你有一个像A<T>这样的通用接口,如果某个对象实际上正在实现A<B>A<C>,你就无法真正检查。

我想知道这是否会导致实际问题。

现在我尝试了这段代码:

static interface A<T> { void foo(T obj); }
static class B implements A<B> {
    public void foo(B obj) { obj.bar(); }       
    void bar() {}
}
static {
    assert (new B() instanceof A<?>);
    ((A<?>) new B()).foo(new Object());
}

这给了我这个错误(对于foo - 调用):

The method foo(capture#1-of ?) in the type Main.A<capture#1-of ?> is not applicable for the arguments (Object)

我想知道为什么会这样。 Eclipse告诉我,foo投射后A<?>的签名是foo(? obj)我认为与foo(Object obj)相同。

assert成功。

我试图弄清楚当我调用foo函数时,它究竟在什么时候投射对象。

另外,如何从foo致电A<?>?这是我实际需要做的事情。或者使用null以外的任何其他参数是不可能的?

一个更真实的例子,我真的很想知道这个:我经常使用Comparable<T>接口。那个案子实际上更复杂;如果这里没有回答,我可能会打开另一个问题。

7 个答案:

答案 0 :(得分:10)

  

我想知道为什么会这样。 Eclipse告诉我,在转换为A之后foo的签名是foo(?obj),我认为它与foo(Object obj)相同。

不,绝对不是。想象一下A<T> List<T> foo(T) add(T)A<?>List<?> List<String> strList = new ArrayList<String>(); List<?> wcList = strList; wcList.add(Integer.valueOf(6)); //possible if add(?) is same as add(Object) //... String str = strList.get(0); 。你应该这样做吗?

foo(?)

当然不是,因为你在最后一行得到了ClassCastException。

{{1}}的真正含义是该方法适用于某些未知但具体的类型。您通常不能调用这些方法,除非您将null作为参数传递,这可以指定为任何引用类型。

答案 1 :(得分:3)

如果您有“foo(? obj)”,那么?可以是任何类型。如果是String,那么你就不能传递Integer。你可以通过的只有null

除非不可避免(例如实施instanceof),否则通常应避免使用equals,尤其是使用泛型。

答案 2 :(得分:0)

编译器是正确的,因为它在编译时进行了 compile-cast 测试。

((A<?>) new B()).foo(new Object());

是错误的,因为编译器期望

((A<?>) new B()).foo(A object)....

意味着它想要任何类型A或其子代。 ObjectA的父级,它与编译测试参数类型不匹配。

答案 3 :(得分:0)

当您检查B的实例是A<?>的实例时,您只需执行与new B() instanceof A相同的操作。你没有真正检查T是如何设置的,它只是“某种东西”。

稍后在演员代码中,您告诉您将B用作A<?>,因此该变量将具有A的特征,但泛型仍然是“东西”。这个“东西”存在,可能是一个指定的类,但你不关心确切的类型。

这就是为什么当你使用foo()参数中的T方法时,你不能在参数中传递“某事”,因为你不知道它是什么,它可以是一个Object但它可能是其他任何东西。

因此,编译器告诉您foo(capture#1-of ?)不适用于参数Object。所需参数是“某事”,但不一定是Object


然后,您何时需要此功能?

例如,如果您使用地图,如果您并不真正关心密钥的类型(例如,如果您只使用values()方法),您可以执行以下操作:< / p>

Map<?, V> m 

这样您将无法使用与地图键相关的功能(但您不关心这一点),但您可以使用任何一种关键。

答案 4 :(得分:0)

不,foo(? obj)实际上与foo(Object obj)不同。不同之处在于,当参数类型为Object时,它明确声明任何类型的对象都是合法的。如果参数类型为?,则该方法声明不知道哪种类型的对象是合法的...因此,除了null之外,没有任何内容合法。

当您考虑List而不是任意接口时,这种情况的推理就变得明显了。看看这个方法:

public void foo(List<?> list) {
  list.add(...); // what can we add here?
}

?表示任何类型的List都可以接受......传入的List可以是List<String>List<Integer>或{ {1}}。没有办法知道。请注意,这只是使用通用参数的方法的问题,例如List<Map<Foo, Bar>>add方法。对于生成(返回)泛型类型的对象的方法,可以使用这样的方法并将结果指定为foo。与使用者方法不同,它不能破坏通用对象的内部状态。

另请注意,当您致电Object时,您正在尝试做违法行为......如果某事(例如((A<?>) new B()).foo(new Object()))不是Object的话传递给B的{​​{1}}方法,它会在运行时爆炸。编译器正确地阻止你这样做。

您可能还想查看我对另一个问题here的回答,该问题解释了有关有界通配符类型的一些问题。

答案 5 :(得分:0)

这是一个简单的解释:

A<?>表示未知参数化类型的A.鉴于

A<?> ref = new B()

ref可以指向任何A:A<ObjectA<String>,任何内容。因此,您无法致电A.foo(new Object()),因为如果A<?>引用,则无法知道ref.foo()接受哪个参数。实际上唯一有效的是ref.foo(null)

答案 6 :(得分:-1)

许多人澄清了关于?的观点,但还没有人真正回答主要问题,所以这里是:

可以像这样调用A#foo

((A) new B()).foo(new Object());

然后演员在<{1}}内 完成。