老问题但没有答案在 Java 中提供了一个具体的解决方案,以干净的方式解决问题。
事实上,这不是一个简单但非常有趣的问题。这是我的贡献。
好的,要调用的方法是在编译时决定的。有没有
避免使用 instanceof 运算符的解决方法?
正如出色的@DaveFar 回答中所说,Java 仅支持单调度方法。
在这种调度模式下,编译器通过依赖声明的参数类型而不是它们的运行时类型来限制方法在编译时立即调用。
我有一个要放入的集合(或列表或数组列表)
字符串值和双精度值。
为了以干净的方式解决答案并使用双重调度,我们必须为操作数据带来抽象。
为什么?
这里用一个幼稚的访问者方法来说明问题:
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”上下文。
所以我们需要在Integer、String 和Object 类中有一个accept() 方法来执行第二次调度:
public void accept(DisplayVisitor visitor){
visitor.visit(this);
}
但不可能!访问的课程是内置课程:String、Integer、Object。
所以我们没有办法添加这个方法。
无论如何,我们不想添加它。
所以要实现双重分派,我们必须能够修改我们想要在第二个分派中作为参数传递的类。
因此,我们不会将 Object 和 List<Object> 作为声明的类型进行操作,而是将 Foo 和 List<Foo> 操作其中 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;
}
}
这是 DisplayVisitor 类访问Foo 子类:
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<Foo> 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=另一个字符串
整数=1
对象=100
改进实施
实际的实现需要为我们想要包装的每个内置类型引入一个特定的包装类。
如前所述,我们无法选择操作双重调度。
但请注意,可以避免 Foo 子类中的重复代码:
private Integer value; // or String or Object
@Override
public Object getValue() {
return value;
}
我们确实可以引入一个抽象的泛型类来保存用户值并提供一个访问器:
public abstract class Foo<T> {
private T value;
public Foo(T value) {
this.value = value;
}
public abstract void accept(DisplayVisitor v);
public T getValue() {
return value;
}
}
现在Foo sublasses 更容易声明:
public class IntegerFoo extends Foo<Integer> {
public IntegerFoo(Integer integer) {
super(integer);
}
@Override
public void accept(DisplayVisitor v) {
v.visit(this);
}
}
public class StringFoo extends Foo<String> {
public StringFoo(String string) {
super(string);
}
@Override
public void accept(DisplayVisitor v) {
v.visit(this);
}
}
public class ObjectFoo extends Foo<Object> {
public ObjectFoo(Object value) {
super(value);
}
@Override
public void accept(DisplayVisitor v) {
v.visit(this);
}
}
并且test()方法应该修改为在List<Foo>声明中为Foo类型声明一个通配符类型(?)。
void test() {
List<Foo<?>> 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);
}
}
事实上,如果真的需要,我们可以通过引入 java 代码生成来进一步简化 Foo 子类。
声明这个子类:
public class StringFoo extends Foo<String> {
public StringFoo(String string) {
super(string);
}
@Override
public void accept(DisplayVisitor v) {
v.visit(this);
}
}
可以像声明一个类并添加注释一样简单:
@Foo(String.class)
public class StringFoo { }
其中Foo 是在编译时处理的自定义注解。