泛型通配符捕获和 Helper 方法
在某些情况下,编译器会推断出通配符的类型,例如,列表可以定义为 List<?>,但是在评估表达式时,编译器会从代码中推断出特定类型,此场景称为通配符捕获。
在大多数情况下,你不必担心通配符捕获,除非你看到包含短语“capture of”的错误消息。
WildcardError 示例在编译时产生捕获错误:
import java.util.List;
public class WildcardError {
void foo(List<?> i) {
i.set(0, i.get(0));
}
}
在此示例中,编译器将 i 输入参数处理为 Object 类型,当 foo 方法调用 List.set(int, E) 时,编译器无法确认插入到列表中的对象的类型,并且会产生错误,发生此类错误时,通常意味着编译器认为你为变量分配了错误的类型,出于这个原因,泛型被添加到 Java 语言中 — 在编译时强制执行类型安全。
由 Oracle 的 JDK 7 javac 实现编译时,WildcardError 示例生成以下错误:
WildcardError.java:6: error: method set in interface List<E> cannot be applied to given types;
i.set(0, i.get(0));
^
required: int,CAP#1
found: int,Object
reason: actual argument Object cannot be converted to CAP#1 by method invocation conversion
where E is a type-variable:
E extends Object declared in interface List
where CAP#1 is a fresh type-variable:
CAP#1 extends Object from capture of ?
1 error
在此示例中,代码尝试执行安全操作,那么如何解决编译器错误?你可以通过编写捕获通配符的私有 Helper 方法来修复它,在这种情况下,你可以通过创建私有 Helper 方法 fooHelper 来解决此问题,如 WildcardFixed 中所示:
public class WildcardFixed {
void foo(List<?> i) {
fooHelper(i);
}
// Helper method created so that the wildcard can be captured
// through type inference.
private <T> void fooHelper(List<T> l) {
l.set(0, l.get(0));
}
}
由于 Helper 方法,编译器使用推断来确定 T 是调用中的 CAP#1(捕获变量),该示例现在成功编译。
按照惯例,Helper 方法通常命名为 originalMethodNameHelper。
现在考虑一个更复杂的例子,WildcardErrorBad:
import java.util.List;
public class WildcardErrorBad {
void swapFirst(List<? extends Number> l1, List<? extends Number> l2) {
Number temp = l1.get(0);
l1.set(0, l2.get(0)); // expected a CAP#1 extends Number,
// got a CAP#2 extends Number;
// same bound, but different types
l2.set(0, temp); // expected a CAP#1 extends Number,
// got a Number
}
}
在这个例子中,代码正在尝试不安全的操作,例如,考虑以下对 swapFirst 方法的调用:
List<Integer> li = Arrays.asList(1, 2, 3);
List<Double> ld = Arrays.asList(10.10, 20.20, 30.30);
swapFirst(li, ld);
List<Integer> 和 List<Double> 都符合 List<? extends Number> 的标准,从 Integer 值列表中获取项目并尝试将其放入 Double 值列表中显然是不正确的。
使用 Oracle 的 JDK javac 编译器编译代码会产生以下错误:
WildcardErrorBad.java:7: error: method set in interface List<E> cannot be applied to given types;
l1.set(0, l2.get(0)); // expected a CAP#1 extends Number,
^
required: int,CAP#1
found: int,Number
reason: actual argument Number cannot be converted to CAP#1 by method invocation conversion
where E is a type-variable:
E extends Object declared in interface List
where CAP#1 is a fresh type-variable:
CAP#1 extends Number from capture of ? extends Number
WildcardErrorBad.java:10: error: method set in interface List<E> cannot be applied to given types;
l2.set(0, temp); // expected a CAP#1 extends Number,
^
required: int,CAP#1
found: int,Number
reason: actual argument Number cannot be converted to CAP#1 by method invocation conversion
where E is a type-variable:
E extends Object declared in interface List
where CAP#1 is a fresh type-variable:
CAP#1 extends Number from capture of ? extends Number
WildcardErrorBad.java:15: error: method set in interface List<E> cannot be applied to given types;
i.set(0, i.get(0));
^
required: int,CAP#1
found: int,Object
reason: actual argument Object cannot be converted to CAP#1 by method invocation conversion
where E is a type-variable:
E extends Object declared in interface List
where CAP#1 is a fresh type-variable:
CAP#1 extends Object from capture of ?
3 errors
这里没有 Helper 方法来解决这个问题,因为代码根本就是错误的。
上一篇:泛型通配符