How to iterate over this generic List with wildcards?
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 m开发者_开发问答ember 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
.
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.
This is possible using reflection
Try this
Method m=this.getClass().getMethod("wash", f.getClass());
m.invoke(this, f.getClass().cast(f));
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.
精彩评论