给定某个类SomeBaseClass
,这两个方法声明是等价的吗?
public <T extends SomeBaseClass> void myMethod(Class<T> clz)
和
public void myMethod(Class<? extends SomeBaseClass> clz)
对于调用者:是的,它们是等价的。
方法内部的代码:no.
不同之处在于,在第一个示例的代码中,您可以使用类型T(例如保存由clz.newInstance()
创建的对象),而在第二个示例中,您不能使用。
不,他们不是。对于第一个定义,您可以在方法定义中使用类型T,例如创建ArrayList<T>
或返回T。对于第二个定义,这是不可能的。
有界通配符受某些限制以避免堆污染。
当你使用通配符?扩展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类型的
当你使用?你知道你可以写一般的信息,但是你不能确定你读到的是什么类型。
例如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的祖先中的任何对象。因此,它不能保证您得到的将是一个String。对吧?
在任何带有有界通配符的声明中,您也会受到相同的限制。这些通常被称为get/put原则。
通过使用类型参数T,您改变了故事,从方法的角度来看,您不是使用有界通配符,而是使用实际类型,因此您可以"获取"one_answers"放入"内容到类的实例中,编译器不会抱怨。
例如,考虑集合中的代码。排序方法。如果按照如下方式编写方法,将得到编译错误:
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);
这是不可能实现的。
总之,正如其他人所说,从调用者的角度来看,它们是相似的,但从实现的角度来看,它们不是。
No。在我的脑海中,我能想到以下不同之处:
-
这两个版本不是覆盖对等的。例如,
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; }
编译。
@Mark @Joachim @Michael
参见JLS3 5.1.10 Capture Conversion中的示例
public static void reverse(List<?> list) { rev(list);}
private static <T> void rev(List<T> list){ ... }
因此<?>
版本可以做<T>
版本可以做的任何事情。
如果运行时是具体化的,这是很容易接受的。无论如何,List<?>
对象必须是某个特定的非通配符X
的List<X>
对象,并且我们可以在运行时访问这个X
。所以使用List<?>
和List<T>
没有区别
对于类型擦除,我们无法访问T
或X
,因此也没有区别。我们可以将T
插入List<T>
-但是如果T
对调用是私有的,和被擦除,您可以在哪里获得T
对象?有两种可能:
T
对象已经存储在List<T>
中。所以我们在操作元素本身。如reverse/rev
示例所示,对List<?>
执行此操作没有问题它来自带外。程序员还做了其他安排,以便保证其他地方的对象在调用时为
T
类型。必须进行未检查的强制转换以覆盖编译器。同样,对List<?>
做同样的事情没有问题