给定一些类SomeBaseClass
,这两个方法声明是等效的吗?
public <T extends SomeBaseClass> void myMethod(Class<T> clz)
和
public void myMethod(Class<? extends SomeBaseClass> clz)
答案 0 :(得分:12)
对于来电者:是的,他们是等同的。
对于方法内的代码:no。
不同之处在于,在第一个示例的代码中,您可以使用类型T(例如,用于保存由clz.newInstance()
创建的对象),而在第二个示例中则不能。
答案 1 :(得分:3)
不,他们不是。使用第一个定义,您可以在方法定义中使用类型T,例如创建ArrayList<T>
或返回T.使用第二个定义,这是不可能的。
答案 2 :(得分:3)
有界通配符受到一定限制,以避免堆污染。
使用通配符时?扩展X你知道你可以阅读通用信息,但你不能写。
例如
List<String> jedis = new ArrayList<String>();
jedis.add("Obiwan");
List<? extends CharSequence> ls = jedis
CharSequence obiwan = ls.get(0); //Ok
ls.add(new StringBuffer("Anakin")); //Not Ok
当您尝试将CharSequence(即StringBuffer)添加到集合时,编译器避免了堆污染。因为编译器无法确定(由于通配符)集合的实际实现是StringBuffer类型。
什么时候使用?超级X你知道你可以写一般信息,但你不能确定你所读的类型。
例如
List<Object> jedis = new ArrayList<Object>();
jedis.add("Obiwan");
List<? super String> ls = jedis;
ls.add("Anakin"); //Ok
String obiwan = ls.get(0); //Not Ok, we can´t be sure list is of Strings.
在这种情况下,由于通配符,编译器知道集合的实际实现可能是String的祖先中的任何内容。因此,它不能保证你将得到的将是一个字符串。正确?
在具有有界通配符的任何声明中,您也会遇到同样的限制。这些通常被称为get / put原则。
通过使用类型参数T,您可以更改故事,从方法角度来看,您没有使用有界通配符而是实际类型,因此您可以“获取”并“将”放入类的实例中,编译器将不要抱怨。
例如,考虑Collections.sort方法中的代码。如果我们编写如下的方法,我们会得到一个编译错误:
public static void sort(List<? extends Number> numbers){
Object[] a = numbers.toArray();
Arrays.sort(a);
ListIterator<? extends Number> i = numbers.listIterator();
for (int j=0; j<a.length; j++) {
i.next();
i.set((Number)a[j]); //Not Ok, you cannot be sure the list is of Number
}
}
但如果你这样写,你就可以开展工作
public static <T extends Number> void sort(List<T> numbers){
Object[] a = numbers.toArray();
Arrays.sort(a);
ListIterator<T> i = numbers.listIterator();
for (int j=0; j<a.length; j++) {
i.next();
i.set((T)a[j]);
}
}
你甚至可以使用带有通配符的集合来调用方法,这要归功于一个叫做捕获转换的东西:
List<? extends Number> ints = new ArrayList<Integer>();
List<? extends Number> floats = new ArrayList<Float>();
sort(ints);
sort(floats);
否则无法实现。
总之,正如其他人从来电者的观点来看他们是相似的,从实施的角度来看,他们不是。
答案 3 :(得分:1)
没有。最重要的是,我可以想到以下不同之处:
这两个版本不是覆盖等效的。例如,
class Foo {
public <T extends SomeBaseClass> void myMethod(Class<T> clz) { }
}
class Bar extends Foo {
public void myMethod(Class<? extends SomeBaseClass> clz) { }
}
无法编译:
名称冲突:类型为Bar的myMethod(Class)方法与Foo类型的myMethod(Class)具有相同的擦除但不覆盖它
如果类型参数在方法签名中出现多次,则它始终表示相同的类型,但如果通配符出现多次,则每次出现可能引用不同的类型。例如,
<T extends Comparable<T>> T max(T a, T b) {
return a.compareTo(b) > 0 ? a : b;
}
编译,但
Comparable<?> max(Comparable<?> a, Comparable<?> b) {
return a.compareTo(b) > 0 ? a : b;
}
没有,因为后者可能被
调用max(Integer.MAX_VALUE, "hello");
方法体可以使用类型参数引用调用者使用的实际类型,但不使用通配符类型。例如:
<T extends Comparable<T>> T max(T... ts) {
if (ts.length == 0) {
return null;
}
T max = ts[0];
for (int i = 1; i < ts.length; i++) {
if (max.compareTo(ts[i]) > 0) {
max = ts[i];
}
}
return max;
}
编译。
答案 4 :(得分:0)
@Mark @Joachim @Michael
参见JLS3 5.1.10捕获转换中的示例
public static void reverse(List<?> list) { rev(list);}
private static <T> void rev(List<T> list){ ... }
因此<?>
版本可以执行<T>
版本可以执行的任何操作。
如果运行时具体化,这很容易接受。无论如何,List<?>
对象必须是某个特定非通配符List<X>
的{{1}}对象,我们可以在运行时访问此X
。因此使用X
或List<?>
使用类型擦除,我们无法访问List<T>
或T
,因此也没有区别。我们可以在X
中插入T
- 但是如果List<T>
对于调用是私有的,那么您可以在哪里获得T
个对象,和抹去?有两种可能性:
T
对象已存储在T
中。所以我们自己操纵元素。正如List<T>
示例所示,对reverse/rev
执行此操作没有问题
来自带外。程序员还有其他的安排,因此保证其他地方的对象可以是List<?>
类型的调用。必须完成未经检查的转换才能覆盖编译器。同样,对T