如何使用通配符迭代此通用List?

时间:2010-12-12 07:26:55

标签: java generics collections iteration wildcard

我有一个扩展另一个类的对象列表:

List<? extends Fruit> arguments;

现在,我想在这些对象上调用一个方法。对于扩展wash的每个类,调用类都有一个方法Fruit,但不是Fruit抽象类的类:

void wash( Apple a);
void wash( Peach p);

如何将方法清洗应用于arguments中的所有元素?这不起作用,因为我的洗涤方法不接受Fruit参数:

for( Fruit f: arguments)
    this.wash( f); // the wash() method is not a member of Fruit

有没有办法解决这个问题,而无需制定一个伞式方法wash( Fruit)?因为有几十种wash( ? extends Fruit)方法......

编辑:我正在谈论的“调用类”是访问者。我不能改变任何Fruit类/子类。我只能为访客编程。这意味着无法将wash()方法(或任何其他方法)添加到抽象类Fruit

3 个答案:

答案 0 :(得分:7)

欢迎来到Double Dynamic Dispatch的世界。

AFAIK,你无法在Java上轻松完成。您可以通过两种方式完成:quick'n'dirty和Visitor方式:

Quick'n'dirty

你需要询问对象的类型,所以你需要在Fruit上使用一个wash方法,它会根据类型将调用重定向到正确的函数:

public void wash(Fruit f)
{
   if(f instanceof Apple)
   {
      wash((Apple) f) ;
   }
   else if(f instanceof Peach)
   {
      wash((Peach) f) ;
   }
   else
   {
      // handle the error, usually through an exception
   }
}

quick'n'dirty的问题是编译器不会告诉你有一个新的有效Fruit(例如Orange)当前没有被wash方法处理。

访问者

您可以将访客模式用于水果:

public abstract class Fruit
{
   // etc.
   public abstract void accept(FruitVisitor v) ;
}

public class Apple extends Fruit
{
   // etc.
   public void accept(FruitVisitor v)
   {
      v.visit(this) ;
   }
}

public class Peach extends Fruit
{
   // etc.
   public void accept(FruitVisitor v)
   {
      v.visit(this) ;
   }
}

并将访客定义为:

public interface class FruitVisitor
{
   // etc.

   // Note that there are no visit method for Fruit
   // this is not an error

   public void visit(Apple a) ;
   public void visit(Peach p) ;
}

然后,您的洗衣案的访客:

public class FruitVisitorWasher : implements FruitVisitor
{
   // etc.

   // Note that there are no visit method for Fruit
   // this is not an error

   // Note, too, that you must provide a wash method in
   // FruitVisitorWasher (or use an anonymous class, as
   // in the example of the second edit to access the
   // wash method of the outer class)

   public void visit(Apple a)
   {
      wash(a) ;
   }

   public void visit(Peach p)
   {
      wash(p) ;
   }
}

最后,你的代码可能是

FruitVisitorWasher fvw = new FruitVisitorWasher() ;

for( Fruit f: arguments)
{
   f.accept(fvw) ;
}

Etvoilà...

如果您添加了另一个Fruit(例如Orange),并且您忘记更新FruitVisitor模式以支持它,则访问者模式具有编译器将告诉您的优势。

然后,访客模式是可扩展的:你可以拥有一个FruitVisitorWasher,一个FruitVisitorEater,一个FruitVisitorWhatever,添加它们而不需要修改Fruit,也不需要修改Apple,Peach等。

但是,有一个陷阱,你必须手动在每个Fruit类中写入accept方法(这是一个复制/粘贴动作),因为这个方法可以完成所有“知道”正确的Fruit类型的工作。

修改

如果你选择Quick'n'dirty解决方案,Samuel Parsonage的解决方案可能比我的更好:

How to iterate over this generic List with wildcards?

利用了Java的反射(我是一个C ++编码器,因此反射不是一个自然的解决方案......我对此不好......)。我发现他的解决方案非常优雅,即使它闻起来有点气味(所有检查都将在运行时完成,所以你最好确保一切正常......再次,通过C ++背景:如果可以做某事,或者错误可以在编译时检测到,应该尽可能避免在运行时移动它)

编辑2

John Assymptoth评论道:

  

写入时的访客模式不是一个选项,因为我无法将方法清洗添加到Fruit。

所以我会提供内联代码来证明wash()不会在Fruit内部工作。

(我将FruitVisitor从抽象类改为接口,这更好)

让我们假设for循环位于Foo类的bar方法中,它有自己的清洗方法:

public class Foo
{
   public wash(Apple a) { /* etc. */ }
   public wash(Peach p) { /* etc. */ }
   public bar(List<? extends Fruit> arguments)
   {
      for( Fruit f: arguments)
      {
         wash(f) ; // we wand the right wash method called.
      }
   }
}

您希望调用正确的清洗方法,因此上面的代码将无法正常工作。

让我们重用FruitVisitor模式来纠正这段代码。我们将在bar方法中使用匿名类:

public class Foo
{
   public void wash(Apple a) { System.out.println("Apple") ; }
   public void wash(Peach p) { System.out.println("Peach") ; }

   public void bar(List<? extends Fruit> arguments)
   {
      FruitVisitor fv = new FruitVisitor()
      {
         public void visit(Apple a)
         {
            wash(a) ; // will call the wash method
                      // of the outer class (Foo)
         }

         public void visit(Peach p)
         {
            wash(p) ; // will call the wash method
                      // of the outer class (Foo)
         }
      } ;

      for(Fruit f: arguments)
      {
         f.accept(fv) ;
      }
   }
}

现在,它有效,而水果中没有洗涤方法。

请注意,此代码是针对1.6 JVM进行测试的,因此我可以在必要时提供完整的代码。

答案 1 :(得分:4)

这可以使用反射

试试这个

Method m=this.getClass().getMethod("wash", f.getClass());
m.invoke(this, f.getClass().cast(f));

答案 2 :(得分:0)

尝试修改

void wash( Apple a);

void wash(List<? extends Fruit> list);

然后在洗涤方法中使用第一个元素。

你仍然需要在Fruit类中使用wash()方法。

Fruit类中的

wash方法将是抽象的,应该在子类中定义。