如何迭代通配符通用?基本上我想内联以下方法:
private <T extends Fact> void iterateFacts(FactManager<T> factManager) {
for (T fact : factManager) {
factManager.doSomething(fact);
}
}
如果此代码在如图所示的单独方法中,则它可以工作,因为泛型方法上下文允许定义通配符类型(此处为T
),可以通过该类型进行迭代。如果尝试内联此方法,则方法上下文消失,并且不再迭代通配符类型。即使在Eclipse中自动执行此操作也会失败并显示以下(无法编译)代码:
...
for (FactManager<?> factManager : factManagers) {
...
for ( fact : factManager) {
factManager.doSomething(fact);
}
...
}
...
我的问题很简单:有没有办法放一些可以迭代的通配符类型,或者这是泛型的限制(意味着不可能这样做)?
答案 0 :(得分:5)
没有。在这种情况下,解决方法是创建一个辅助方法。
JLS有这个例子http://java.sun.com/docs/books/jls/third_edition/html/conversions.html#5.1.10
public static void reverse(List<?> list) { rev(list);}
private static <T> void rev(List<T> list) { ... }
问题是,我们有一个List<?>
对象。我们知道它必须是某些List<X>
的{{1}},我们希望使用X
编写代码。内部编译器确实将通配符转换为类型变量X
,但Java语言不能为程序员提供访问它的直接方法。但是如果有一个接受X
的方法,我们可以将该对象传递给该方法。编译器推断List<T>
并且通话良好。
如果没有类型擦除,T=X
可以在运行时知道,那么Java肯定会给我们一种访问X
的方法。但是截至今天,由于X
在运行时不可用,因此没有多大意义。可以提供纯粹的合成方式,这不太可能比辅助方法更简单。
答案 1 :(得分:3)
类型参数只能在
上定义您需要一个本地块的类型参数,这是不可能的。
是的,我有时也错过了这样的事情。
但是在这里使用非内联方法并没有什么问题 - 如果它提出了一个性能瓶颈,其中内联会有所帮助,Hotspot会再次内联它(不关心类型)。
此外,使用单独的方法可以为其提供描述性名称。
只是一个想法,如果你经常需要这个:
interface DoWithFM {
void <T> run(FactManager<T> t);
}
...
for (FactManager<?> factManager : factManagers) {
...
new DoWithFM() { public <T> run(FactManager<T> factManager) {
for (T fact : factManager) {
factManager.doSomething(fact);
}
}.run(factManager);
...
}
...
答案 2 :(得分:2)
您可以随时回退到Object
for (FactManager<?> factManager : factManagers) {
...
for ( Object fact : factManager) {
factManager.doSomething(fact);
}
...
}
当然,这取决于doSomething
的实际声明。
如果doSomething
被声明为此void doSomething( T fact )
,那么您在此处的使用方法是使用原始类型并吞下unchecked
警告。如果您可以保证FactManager
只能插入同类Facts
,那么这可能是一个不错的解决方案。
for (FactManager factManager : factManagers) { // unchecked warning on this line
...
for ( Object fact : factManager) {
factManager.doSomething(fact);
}
...
}
答案 3 :(得分:2)
好吧,我可以想办法使用内部类来实现它,因为内部类与其封闭类型共享type参数。此外,即使使用通配符,您仍然可以通过通配符捕获转换处理您的集合。
让我创建一个例子。此代码编译并运行正常。但我无法确定内部类的使用对你来说是一个问题。
//as you can see type parameter belongs to the enclosing class
public class FactManager<T> implements Iterable<FactManager<T>.Fact> {
private Collection<Fact> items = new ArrayList<Fact>();
public void doSomething(Fact fact) {
System.out.println(fact.getValue());
}
public void addFact(T value) {
this.items.add(new Fact(value));
}
@Override
public Iterator<Fact> iterator() {
return items.iterator();
}
public class Fact {
//inner class share its enclosing class type parameter
private T value;
public Fact(T value) {
this.value = value;
}
public T getValue() {
return this.value;
}
public void setValue(T value) {
this.value = value;
}
}
public static void main(String[] args) {
List<FactManager<String>> factManagers = new ArrayList<FactManager<String>>();
factManagers.add(new FactManager<String>());
factManagers.get(0).addFact("Obi-wan");
factManagers.get(0).addFact("Skywalker");
for(FactManager<? extends CharSequence> factManager : factManagers){
//process thanks to wildcard capture conversion
procesFactManager(factManager);
}
}
//Wildcard capture conversion can be used to process wildcard-based collections
public static <T> void procesFactManager(FactManager<T> factManager){
for(FactManager<T>.Fact fact : factManager){
factManager.doSomething(fact);
}
}
}
答案 4 :(得分:0)
这更精确地与你定义的方法相匹配(也就是说,如果你可以在FactManagers中使用FactManagers调用iterateFacts(),你就知道FactManager包含了Fact的一些子类的项目。)
for (FactManager<? extends Fact> factManager : factManagers) {
for (Fact fact : factManager) {
factManager.doSomething(fact);
}
}
但是,我倾向于认为你会将FactManager声明为Fact的子类型的通用(只是给出了类的名称),例如
class FactManager<T extends Fact> implements Iterable<T> {
...
}
Eclipse重构失败,因为它无法推断FactManager<?>
包含的对象的类型。