欢迎来到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 进行测试的,因此如有必要,我可以提供完整的代码。