如何迭代通配符泛型?

时间:2011-06-07 21:42:17

标签: java generics foreach

如何迭代通配符通用?基本上我想内联以下方法:

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);
    }
    ...
}
...

我的问题很简单:有没有办法放一些可以迭代的通配符类型,或者这是泛型的限制(意味着不可能这样做)?

5 个答案:

答案 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<?>包含的对象的类型。