我正在刷新我对Java泛型的知识.所以我转向Oracle的优秀教程......并开始为我的同事准备一份演示文稿.我在教程中遇到了关于通配符的部分,其中说:
考虑以下方法,printList:
Run Code Online (Sandbox Code Playgroud)public static void printList(List<Object> list) { ...printList的目标是打印任何类型的列表,但它无法实现该目标 - 它只打印一个Object实例列表; 它不能打印
List<Integer>,List<String>,List<Double>,等等,因为它们不是的亚型List<Object>.要编写通用的printList方法,请使用List<?>:Run Code Online (Sandbox Code Playgroud)public static void printList(List<?> list) {
我明白这是List<Object>行不通的; 但我把代码更改为
static <E> void printObjects(List<E> list) {
for (E e : list) {
System.out.println(e.toString());
}
}
...
List<Object> objects = Arrays.<Object>asList("1", "two");
printObjects(objects);
List<Integer> integers = Arrays.asList(3, 4);
printObjects(integers);
Run Code Online (Sandbox Code Playgroud)
你猜怎么着; 使用List<E>我可以打印不同类型的列表没有任何问题.
长话短说:至少教程表明需要通配符来解决这个问题; 但如图所示,它也可以通过这种方式解决.那么,我错过了什么?!
(旁注:用Java7测试;所以这可能是Java5,Java6的一个问题;但另一方面,Oracle似乎在他们的教程更新方面做得很好)
gex*_*ide 40
使用通用方法的方法比使用通配符的方法更强大,所以是的,您的方法也是可行的.但是,本教程并未声明使用通配符是唯一可行的解决方案,因此本教程也是正确的.
与通用方法相比,使用通配符获得的结果是:由于非泛型方法更容易掌握,因此必须编写更少且界面更"清晰".
为什么泛型方法比通配符方法更强大:为参数指定一个可以引用的名称.例如,考虑一种方法,该方法删除列表的第一个元素并将其添加到列表的后面.使用通用参数,我们可以执行以下操作:
static <T> boolean rotateOneElement(List<T> l){
return l.add(l.remove(0));
}
Run Code Online (Sandbox Code Playgroud)
使用通配符,这是不可能的,因为l.remove(0)会返回capture-1-of-?,但l.add需要capture-2-of-?.即,编译器无法推断出结果与预期的remove类型相同add.这与编译器可以推断两者都是相同类型的第一个示例相反T.此代码无法编译:
static boolean rotateOneElement(List<?> l){
return l.add(l.remove(0)); //ERROR!
}
Run Code Online (Sandbox Code Playgroud)
那么,如果你想使用带通配符的rotateOneElement方法,你能做什么,因为它比通用解决方案更容易使用?答案很简单:让通配符方法调用泛型方法,然后它可以工作:
// Private implementation
private static <T> boolean rotateOneElementImpl(List<T> l){
return l.add(l.remove(0));
}
//Public interface
static void rotateOneElement(List<?> l){
rotateOneElementImpl(l);
}
Run Code Online (Sandbox Code Playgroud)
标准库在许多地方使用这个技巧.其中之一是IIRC,Collections.java
两种解决方案实际上是相同的,只是在第二种解决方案中,您正在命名通配符.当您想在签名中多次使用通配符时,这可以派上用场,但是要确保两者都引用相同的类型:
static <E> void printObjects(List<E> list, PrintFormat<E> format) {
Run Code Online (Sandbox Code Playgroud)
从技术上讲,两者之间没有区别
<E> void printObjects(List<E> list) {
Run Code Online (Sandbox Code Playgroud)
和
void printList(List<?> list) {
Run Code Online (Sandbox Code Playgroud)
另一方面,如果您不止一次使用它,差异就会变得很大.例如
<E> void printObjectsExceptOne(List<E> list, E object) {
Run Code Online (Sandbox Code Playgroud)
完全不同于
void printObjects(List<?> list, Object object) {
Run Code Online (Sandbox Code Playgroud)
您可能会看到第一种情况强制两种类型相同.虽然第二种情况没有限制.
其结果是,如果你要使用的类型参数只有一次,它甚至没有意义的命名.这就是为什么java架构师发明了所谓的通配符参数(最有可能).
通配符参数可避免不必要的代码膨胀并使代码更具可读性.如果您需要两个,则必须回退到类型参数的常规语法.
希望这可以帮助.