【问题标题】:How to iterate over this generic List with wildcards?如何使用通配符遍历这个通用列表?
【发布时间】:2011-05-24 04:05:57
【问题描述】:

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

List<? extends Fruit> arguments;

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

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

如何将方法wash 应用于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

【问题讨论】:

  • 为什么你不想在 Fruit 类中使用wash() 方法?
  • @vishwanath 我认为让水果自己洗是没有意义的。
  • 方法wash 不在 Fruit 类中。它在处理 Fruit 的类中。这个.wash()。我已经编辑了原始帖子。
  • 它的多态性,你可以声明方法抽象表示每个水果都有自己的洗涤类型..
  • @vishwanath :您不能将“doSomething”方法添加到您的 Fruit 类以及代码中每次处理 Fruit 的每个继承类中。一方面,它是不可扩展的(作为你的类的用户,我无法扩展它来添加我的“doSomethingElse”方法,无论如何,你只会用杂乱的方法污染你的类。使用访问者模式。保持您的类很简单,并将代码保留在应该使用的地方。

标签: java generics collections iteration wildcard


【解决方案1】:

欢迎来到Double Dynamic Dispatch 的世界。

AFAIK,你不能在 Java 上轻松做到这一点。您可以通过两种方式做到这一点:quick'n'dirty 和 Visitor 方式:

快不脏

您需要询问对象的类型,因此您需要在 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 的问题是编译器不会告诉你有一个新的有效水果(例如橙子)当前没有被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) ;
}

等等……

Visitor 模式的优势在于,如果您添加了另一个 Fruit(例如 Orange)并在其中编写了 accept 方法,并且如果您忘记更新 FruitVisitor 模式以支持它,编译器会告诉您。

然后,Visitor 模式是可扩展的:您可以拥有 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 Asymptoth 评论道:

您编写的访问者模式不是一个选项,因为我无法将方法wash 添加到 Fruit。

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

(我把FruitVisitor从抽象类改成了接口,这样更好)

假设for循环在Foo类的bar方法里面,它有自己的wash方法:

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

现在,它可以工作了,而且 Fruits 中没有洗涤方法。

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

【讨论】:

  • @gigadot:谢谢。我总是忘记了如何正确编写访问者模式。虽然我理解它及其用途,但我无法正确记住确切的代码,所以我发现尽可能重写一个示例对读者(如果他/她不了解访问者)和我自己都很有用。
  • 很好的答案。不幸的是,您编写的访问者模式不是一个选项,因为我无法将方法wash 添加到 Fruit。 “quick'n'dirty”解决方案是我记得的第一个解决方案,但有几十个水果并没有帮助。不过,塞缪尔牧师的解决方案似乎正是我所需要的。感谢您的帮助。
  • @John Assymptoth :访问者模式不需要清洗方法才能生效。我将编辑我的答案以提供 FruitVisitorWasher 的“内联”版本向您展示。
  • 对不起,我的错。我不是指洗涤方法。我的意思是接受方法。我无法向 Fruit 添加接受方法。
  • 我误读了您的回答。我忘记了我的 Fruit 已经有一个接受方法。我对 this.visit(f) 如此着迷,以至于我忘记了我可以使用 f.accept(this)。谢谢,因为我试图不使用反射。但是,如果无法将接受添加到 Fruit,Samuel 的答案非常紧凑。
【解决方案2】:

这可以通过反射实现

试试这个

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

【讨论】:

  • 我相信这是最好最简洁的解决方案。
  • @gigadot :不是最好的解决方案,因为它过于依赖运行时(如果 f 不是 Fruit,或者如果没有wash(Orange o) 方法会发生什么情况)? Java 是一种强类型语言:编译器应该能够提供帮助。我有一种预感,这种代码可能会很慢(通过名称搜索方法......)。它仍然是最简洁的解决方案,我觉得它非常优雅。
  • 很棒的答案。我的抽象类已经有一个 accept 方法,所以我不需要使用它,否则,这将是一个非常紧凑和有用的解决方案。谢谢。
【解决方案3】:

尝试修改

void wash( Apple a);

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

然后使用wash方法中的第一个元素。

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

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

【讨论】:

  • 他希望它是多态的——有多个wash 方法,他希望它根据类型选择正确的方法。
  • 请注意,虽然在其继承类上覆盖 Fruit 类中的“wash”方法在一种情况下可能没问题,但它仍然是一个很大的先决条件:通常不能添加 (virtual ) 方法在每个本地处理的每个类上(如果只是因为并非所有类都可以轻松修改以处理用户的特殊情况)。访问者模式的存在就是为了优雅地处理这个问题。
猜你喜欢
  • 1970-01-01
  • 2018-12-27
  • 1970-01-01
  • 2011-09-10
  • 2017-10-14
  • 2022-11-01
  • 1970-01-01
  • 1970-01-01
  • 2019-07-11
相关资源
最近更新 更多