我仍在尝试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>
接口。那个案子实际上更复杂;如果这里没有回答,我可能会打开另一个问题。
答案 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或其子代。 Object
是A
的父级,它与编译测试参数类型不匹配。
答案 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<Object
,A<String>
,任何内容。因此,您无法致电A.foo(new Object())
,因为如果A<?>
引用,则无法知道ref.foo()
接受哪个参数。实际上唯一有效的是ref.foo(null)
。
答案 6 :(得分:-1)
许多人澄清了关于?
的观点,但还没有人真正回答主要问题,所以这里是:
可以像这样调用A#foo
:
((A) new B()).foo(new Object());
然后演员在<{1}}内 完成。