如何使用通配符迭代这个通用列表?

发布于 2024-10-07 08:43:22 字数 747 浏览 0 评论 0原文

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

List<? extends Fruit> arguments;

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

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

我怎样才能应用方法清洗arguments中的所有元素?这不起作用,因为我的 Wash 方法不接受 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 中。

I have a List of objects that extend another class:

List<? extends Fruit> arguments;

Now, I want to invoke a method on these objects. The invoking class has a method wash for each of the classes that extend Fruit, but NOT for the Fruit abstract class:

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

How can I apply the method wash to all the elements in arguments? This DOESN'T work, as my wash methods don't accept Fruit arguments:

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

Is there any way to solve this without having to make an umbrella method wash( Fruit)? Because there are dozens of wash( ? extends Fruit) methods...

.

EDIT: The "invoking class" I am talking about is a visitor. And I can't alter any of the Fruit classes/subclasses. I can only program the visitor. This means it isn't possible to add the wash() method (or any other methods, for that matter) to the abstract class Fruit.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(3

痕至 2024-10-14 08:43:22

欢迎来到双重动态调度的世界。

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 的问题是编译器不会告诉您有一个新的有效水果(例如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) ;
}

Et voilà...

访问者模式有优点是,编译器会告诉您是否添加了另一个 Fruit(例如 Orange)并在其中编写了接受方法,以及您是否忘记更新 FruitVisitor 模式以支持它。

然后,Visitor 模式是可扩展的:您可以有一个 FruitVisitorWasher、一个 FruitVisitorEater、一个 FruitVisitorWhatever,添加它们而无需修改 Fruit 或 Apple、Peach 等。

不过,有一个陷阱,您必须在每个 Fruit 类中手动编写接受方法(这是一个复制/粘贴操作),因为正是这个方法完成了“了解”正确的水果类型的所有工作。

编辑

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

如何使用通配符迭代此通用列表?

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

编辑 2

John Assymptoth 评论道:

您编写的访问者模式不是一个选项,因为我无法将方法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.
      }
   }
}

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

让我们重用 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 中没有 Wash 方法。

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

Welcome to the world of Double Dynamic Dispatch.

AFAIK, you can't do it easily on Java. You can do it two ways : The quick'n'dirty, and the Visitor way:

Quick'n'dirty

You need to ask the type of the object, so you'll need a wash method on Fruit which will redirect the call to the right function according to its type:

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

The problem with the quick'n'dirty is that the compiler won't tell you there is a new valid Fruit (e.g. Orange) that is not currently handled by the wash method.

Visitor

You could use the Visitor pattern to Fruit:

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

And define the Visitor as :

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

And then, the Visitor for your wash case:

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

In the end, you code could be

FruitVisitorWasher fvw = new FruitVisitorWasher() ;

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

Et voilà...

The Visitor pattern has the advantage the compiler will tell you if you added another Fruit (e.g. Orange) in which you coded an accept method and if you forgot to update the FruitVisitor pattern to support it.

And then, the Visitor pattern is extensible: You can have a FruitVisitorWasher, a FruitVisitorEater, a FruitVisitorWhatever, adding them without needing to modify Fruit nor Apple, Peach, etc..

One trap, though, you must manually write in each Fruit class the accept method (which is a copy/paste action) because it is this method which does all the work of "knowing" the right Fruit type.

Edit

If you go for the Quick'n'dirty solution, the Samuel Parsonage's solution could be even better than mine:

How to iterate over this generic List with wildcards?

which makes use of Java's reflection (I'm a C++ coder, so reflection doesn't come as a natural solution... My bad on this...). I find his solution quite elegant, even if it smells somehow (all the checks will be done in the runtime, so you'd better make sure everything's ok... Again, by C++ background: If something can be done, or an error can be detected at compile time, moving it at runtime should be avoided as much as possible)

Edit 2

John Assymptoth commented:

The Visitor pattern as you write it isn't an option, since I can't add the method wash to Fruit.

So I'll offer the inline code to prove wash() is not expected to be inside Fruit to work.

(I changed FruitVisitor from an abstract class into an interface, which is better)

Let's imagine the for loop is inside the bar method of the Foo class, which has its own wash method:

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

You want the right wash method called, so the code above won't work correctly.

Let's reuse the FruitVisitor pattern to correct this code. We will use an anonymous class inside the bar method:

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

Now, it works, and there are no wash method in Fruits.

Note that this code was tested against a 1.6 JVM, so I can provide the complete code if necessary.

迷离° 2024-10-14 08:43:22

这可以使用反射

来尝试这个

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

This is possible using reflection

Try this

Method m=this.getClass().getMethod("wash", f.getClass());
m.invoke(this, f.getClass().cast(f));
未央 2024-10-14 08:43:22

尝试修改

void wash( Apple a);

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

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

您仍然需要 Fruit 类中的 Wash() 方法。

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

Try modifying

void wash( Apple a);

to

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

And then use the first element in wash method.

You still need to have the wash() method in Fruit class.

wash method in Fruit class will be abstract and should be defined in subclasses.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文