Joh*_*oth 10 java generics iteration collections wildcard
我有一个扩展另一个类的对象列表:
List<? extends Fruit> arguments;
Run Code Online (Sandbox Code Playgroud)
现在,我想在这些对象上调用一个方法.调用类wash为每个扩展的类都有一个方法Fruit,但不适用于Fruit抽象类:
void wash( Apple a);
void wash( Peach p);
Run Code Online (Sandbox Code Playgroud)
如何将方法清洗应用于所有元素arguments?这不起作用,因为我的洗涤方法不接受Fruit参数:
for( Fruit f: arguments)
this.wash( f); // the wash() method is not a member of Fruit
Run Code Online (Sandbox Code Playgroud)
有没有办法解决这个问题,而不必采取伞式方法wash( Fruit)?因为有几十种wash( ? extends Fruit)方法......
.
编辑:我所说的"调用类"是访问者.我不能改变任何Fruit类/子类.我只能为访客编程.这意味着无法将该wash()方法(或任何其他方法)添加到抽象类中Fruit.
欢迎来到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
}
}
Run Code Online (Sandbox Code Playgroud)
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) ;
}
}
Run Code Online (Sandbox Code Playgroud)
并将访客定义为:
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) ;
}
Run Code Online (Sandbox Code Playgroud)
然后,您的洗衣箱访客:
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) ;
}
}
Run Code Online (Sandbox Code Playgroud)
最后,您的代码可能是
FruitVisitorWasher fvw = new FruitVisitorWasher() ;
for( Fruit f: arguments)
{
f.accept(fvw) ;
}
Run Code Online (Sandbox Code Playgroud)
Etvoilà......
访问者模式具有编译器将告诉您的优势,如果您添加了另一个Fruit(例如Orange),其中您编写了一个accept方法,并且您忘记更新FruitVisitor模式以支持它.
然后,访客模式是可扩展的:您可以拥有FruitVisitorWasher,FruitVisitorEater,FruitVisitorWhatever,添加它们而无需修改Fruit或Apple,Peach等.
但是,有一个陷阱,你必须在每个Fruit类中手动编写accept方法(这是一个复制/粘贴操作),因为这个方法可以完成所有"知道"正确的Fruit类型的工作.
如果你选择Quick'n'dirty解决方案,Samuel Parsonage的解决方案可能比我的更好:
它利用了Java的反射(我是一个C++编码器,所以反射并不是一个自然的解决方案......我对此不好......).我发现他的解决方案非常优雅,即使它闻起来有点气味(所有检查都将在运行时完成,所以你最好确保一切正常......再次,通过C++背景:如果可以做某事,或者错误可以在编译时检测到,应该尽可能避免在运行时移动它)
John Assymptoth评论道:
写入时的访问者模式不是一个选项,因为我无法将方法清洗添加到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.
}
}
}
Run Code Online (Sandbox Code Playgroud)
您希望调用正确的清洗方法,因此上面的代码将无法正常工作.
让我们重用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) ;
}
}
}
Run Code Online (Sandbox Code Playgroud)
现在,它有效,而且水果中没有洗涤方法.
请注意,此代码是针对1.6 JVM进行测试的,因此如有必要,我可以提供完整的代码.