旧问题,但没有答案提供Java的具体解决方案,以一个干净的方式解决问题。
事实上,不容易但非常有趣的问题。这是我的贡献。
好的,要调用的方法是在编译时决定的。有没有解决方法来避免使用instanceof运算符?
正如优秀的@DaveFar回答中所述,Java仅支持单调度方法。
在这种调度模式下,编译器通过依赖声明的参数类型而不是它们的运行时类型来编译方法,以便在编译时立即调用。
我有一个集合(或列表或数组列表),我想在其中放置String值和double值。
为了以一种干净的方式解决问题并使用双重调度,我们必须为操纵数据带来抽象。
为什么?
这里有一个天真的访问者方法来说明问题:
public class DisplayVisitor {
void visit(Object o) {
System.out.println("object"));
}
void visit(Integer i) {
System.out.println("integer");
}
void visit(String s) {
System.out.println("string"));
}
}
现在,问题:访问类如何调用visit()方法?
双调度实现的第二次调度依赖于接受访问的类的“this”上下文。
所以我们需要在accept(),Integer和String类中使用Object方法来执行第二次调度:
public void accept(DisplayVisitor visitor){
visitor.visit(this);
}
但不可能!访问类是内置类:String,Integer,Object。
所以我们无法添加此方法。
无论如何,我们不想添加它。
因此,为了实现双分派,我们必须能够在第二个分派中修改我们想要作为参数传递的类。
因此,我们将操纵Object和List而不是操纵Foo和List,其中Foo类是包含用户值的包装器。
这是Foo界面:
public interface Foo {
void accept(DisplayVisitor v);
Object getValue();
}
getValue()返回用户值。
它将Object指定为返回类型,但Java支持协方差返回(从1.5版本开始),因此我们可以为每个子类定义更具体的类型以避免向下转换。
ObjectFoo
public class ObjectFoo implements Foo {
private Object value;
public ObjectFoo(Object value) {
this.value = value;
}
@Override
public void accept(DisplayVisitor v) {
v.visit(this);
}
@Override
public Object getValue() {
return value;
}
}
StringFoo
public class StringFoo implements Foo {
private String value;
public StringFoo(String string) {
this.value = string;
}
@Override
public void accept(DisplayVisitor v) {
v.visit(this);
}
@Override
public String getValue() {
return value;
}
}
IntegerFoo
public class IntegerFoo implements Foo {
private Integer value;
public IntegerFoo(Integer integer) {
this.value = integer;
}
@Override
public void accept(DisplayVisitor v) {
v.visit(this);
}
@Override
public Integer getValue() {
return value;
}
}
这是访问Foo子类的DisplayVisitor类:
public class DisplayVisitor {
void visit(ObjectFoo f) {
System.out.println("object=" + f.getValue());
}
void visit(IntegerFoo f) {
System.out.println("integer=" + f.getValue());
}
void visit(StringFoo f) {
System.out.println("string=" + f.getValue());
}
}
这是一个测试实现的示例代码:
public class OOP {
void test() {
List foos = Arrays.asList(new StringFoo("a String"),
new StringFoo("another String"),
new IntegerFoo(1),
new ObjectFoo(new AtomicInteger(100)));
DisplayVisitor visitor = new DisplayVisitor();
for (Foo foo : foos) {
foo.accept(visitor);
}
}
public static void main(String[] args) {
OOP oop = new OOP();
oop.test();
}
}
输出:
string =一个字符串
string =另一个String
整数= 1
对象= 100
改进实施
实际的实现需要为我们想要包装的每个buit-in类型引入一个特定的包装类。如上所述,我们无法选择双重调度。
但请注意,可以避免Foo子类中重复的代码:
private Integer value; // or String or Object
@Override
public Object getValue() {
return value;
}
我们确实可以引入一个包含用户值的抽象泛型类,并提供一个访问器:
public abstract class Foo {
private T value;
public Foo(T value) {
this.value = value;
}
public abstract void accept(DisplayVisitor v);
public T getValue() {
return value;
}
}
现在qazxsw poi子类更轻松地声明:
Foo
并且应修改public class IntegerFoo extends Foo {
public IntegerFoo(Integer integer) {
super(integer);
}
@Override
public void accept(DisplayVisitor v) {
v.visit(this);
}
}
public class StringFoo extends Foo {
public StringFoo(String string) {
super(string);
}
@Override
public void accept(DisplayVisitor v) {
v.visit(this);
}
}
public class ObjectFoo extends Foo {
public ObjectFoo(Object value) {
super(value);
}
@Override
public void accept(DisplayVisitor v) {
v.visit(this);
}
}方法以在test()声明中为?类型声明通配符类型(Foo)。
List
事实上,如果真的需要,我们可以通过引入java代码生成来简化进一步的void test() {
List> foos = Arrays.asList(new StringFoo("a String object"),
new StringFoo("anoter String object"),
new IntegerFoo(1),
new ObjectFoo(new AtomicInteger(100)));
DisplayVisitor visitor = new DisplayVisitor();
for (Foo> foo : foos) {
foo.accept(visitor);
}
}子类。
声明此子类:
Foo
可以像声明一个类并在其上添加注释一样简单:
public class StringFoo extends Foo {
public StringFoo(String string) {
super(string);
}
@Override
public void accept(DisplayVisitor v) {
v.visit(this);
}
}
其中@Foo(String.class)
public class StringFoo { }是在编译时处理的自定义注释。