【问题标题】:How to iterate over a wildcard generic?如何遍历通配符泛型?
【发布时间】:2011-09-10 10:43:24
【问题描述】:

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

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

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

【问题讨论】:

  • 如果您要内联该方法,您可能确实知道factManager 的类型。目前该变量是如何声明的?
  • if one tries to inline this method 是什么意思。它几乎已经内联了。
  • 您可以使用反射,但不会出现编译错误,而是会出现运行时错误。最后但并非最不重要的一点是,您的所有代码都需要 try catch 保护
  • 为什么FactManager 外部的东西将它用作Iterable 并循环通过它包含的Facts,然后让相同的FactManager 对它们做些什么?封装那个! FactManager 会知道它有什么样的Facts。
  • @Colin:也许还有一些过滤正在进行,或者外部事物必须决定必须调用 FactManager 的哪个方法?

标签: java generics foreach


【解决方案1】:

没有。在这种情况下,解决方法是创建一个辅助方法。

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&lt;?&gt; 对象。我们知道它必须是一些X 中的List&lt;X&gt;,我们想使用X 编写代码。内部编译器确实将通配符转换为类型变量X,但Java 语言不为程序员提供访问它的直接方式。但是如果有一个方法接受List&lt;T&gt;,我们可以将对象传递给该方法。编译器推断 T=X 并且调用良好。

如果没有类型擦除,X 可以在运行时知道,那么 Java 肯定会为我们提供访问X 的方法。但是截至今天,由于X 在运行时不可用,因此没有多大意义。可以提供一种纯粹的合成方法,这不太可能比辅助方法解决方法更简单。

【讨论】:

    【解决方案2】:

    类型参数只能定义在

    • 类型(即类/接口),
    • 方法和
    • 构造函数。

    你需要一个本地块的类型参数,这是不可能的。

    是的,我有时也会错过这样的事情。

    但是在此处使用非内联方法并没有真正的问题 - 如果它存在性能瓶颈而内联会有所帮助,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);
        ...
    }
    ...
    

    【讨论】:

      【解决方案3】:

      您可以随时退回到Object

      for (FactManager<?> factManager : factManagers) {
          ...
          for ( Object fact : factManager) {
              factManager.doSomething(fact);
          }
          ...
      }
      

      当然,这取决于doSomething 的实际声明。

      如果doSomething 被声明为void doSomething( T fact ),那么你的办法就是使用原始类型并吞下unchecked 警告。如果你能保证FactManager只能插入同质的Facts,那么这可能是一个OK的解决方案。

      for (FactManager factManager : factManagers) { // unchecked warning on this line
          ...
          for ( Object fact : factManager) {
              factManager.doSomething(fact);
          }
          ...
      }
      

      【讨论】:

      • 正如您所假设的,该方法在FactManager 中声明为void doSomething(T fact),因此代码需要额外转换:factManager.doSomething((Fact)fact); 这将是最实用的解决方案,但我想知道这是我的错还是泛型的限制。
      【解决方案4】:

      好吧,我可以想办法使用内部类来实现,因为内部类与其封闭类型共享类型参数。此外,由于通配符捕获转换,即使使用通配符,您仍然可以处理您的集合。

      让我创建一个示例。此代码编译并运行良好。但我不能确定使用内部类是否对你来说是个问题。

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

      【讨论】:

      • 这是一个非常好的和创造性的解决方案。我相信在某些情况下我会使用它,遗憾的是在给定的情况下我不能。
      • 这与在此处使用 Fact&lt;T&gt; 并没有真正的不同,并且并没有真正消除对单独的 procesFactManager 方法的需要(据我所知,这确实是问题的重点。 )
      【解决方案5】:

      这更精确地匹配您定义的方法(也就是说,如果您可以使用 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&lt;?&gt; 包含的对象的类型。

      【讨论】:

      • 正如 Alexander Pogrebnyak 的回答所指出的,该方法在 FactManager 中声明为 void doSomething(T fact),因此代码会导致以下错误消息:“方法 doSomething(capture#4- FactManager 类型中的 of ? extends Fact) 不适用于参数 (Fact)。"
      猜你喜欢
      • 2014-05-26
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2020-03-20
      • 1970-01-01
      • 2011-05-24
      相关资源
      最近更新 更多