在Java中重载和多次调度

我有一个集合(或列表或数组列表),我想在其中放置String值和double值。 我决定使它成为对象的集合并使用重载ond多态,但我做错了。

我做了一点测试:

public class OOP { void prova(Object o){ System.out.println("object"); } void prova(Integer i){ System.out.println("integer"); } void prova(String s){ System.out.println("string"); } void test(){ Object o = new String(" "); this.prova(o); // Prints 'object'!!! Why?!?!? } public static void main(String[] args) { OOP oop = new OOP(); oop.test(); // Prints 'object'!!! Why?!?!? } } 

在测试中,似乎参数类型是在编译时决定的,而不是在运行时决定的。 这是为什么?

这个问题与以下内容有关:

多态性与重写与重载
尝试尽可能简单地描述多态性

编辑:

好的,要调用的方法是在编译时决定的。 是否有解决方法以避免使用instanceof运算符?

这篇文章是秒voo的回答,并提供了有关后期绑定的替代方案的详细信息。

通用JVM仅使用单个调度 :运行时类型仅考虑接收器对象; 对于方法的参数,考虑静态类型。 使用方法表 (类似于C ++的虚拟表),使用优化的高效实现非常容易。 您可以在HotSpot Wiki中找到详细信息 。

如果您想为参数多次调度 ,请查看

  • 时髦的 。 但据我所知,这有一个过时的,缓慢的多调度实现(参见例如这种性能比较 ),例如没有缓存。
  • clojure ,但这与Java完全不同。
  • MultiJava ,为Java提供多个调度。 此外,您可以使用
    • this.resend(...)而不是super(...)来调用封闭方法的最具体的重写方法;
    • 值调度(下面的代码示例)。

如果你想坚持使用Java ,你可以

  • 通过在较细粒度的层次结构上移动重载方法来重新设计应用程序。 Josh Bloch的Effective Java ,第41项(明智地使用重载)给出了一个例子;
  • 使用一些设计模式,例如Strategy,Visitor,Observer。 这些通常可以解决与多个调度相同的问题(即在那些情况下,您使用多个调度对这些模式有简单的解决方案)。

价值调度:

 class C { static final int INITIALIZED = 0; static final int RUNNING = 1; static final int STOPPED = 2; void m(int i) { // the default method } void m(int@@INITIALIZED i) { // handle the case when we're in the initialized `state' } void m(int@@RUNNING i) { // handle the case when we're in the running `state' } void m(int@@STOPPED i) { // handle the case when we're in the stopped `state' } } 

你想要的是双重或更多的一般多重调度 ,实际上是用其他语言实现的东西(常见的lisp想到)

据推测,java没有它的主要原因是因为它会导致性能损失,因为重载决策必须在运行时完成而不是编译时。 通常的方法是访客模式 – 非常难看,但就是这样。

在调用重载的方法时,Java会根据传递给函数的变量类型选择最严格的类型。 它不使用实际实例的类型。

这不是多态性,你只是重载一个方法并用对象类型的参数调用它

Java中的所有东西都是Object / object(原始类型除外)。 将字符串和整数存储为对象,然后在调用prove方法时,它们仍称为对象。 您应该查看instanceof关键字。 检查此链接

 void prove(Object o){ if (o instanceof String) System.out.println("String"); .... } 

旧问题,但没有答案提供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”上下文。
所以我们需要在IntegerStringObject类中使用accept()方法来执行第二次调度:

 public void accept(DisplayVisitor visitor){ visitor.visit(this); } 

但不可能! 被访问的类是内置类: StringIntegerObject
所以我们无法添加此方法。
无论如何,我们不想添加它。

因此,为了实现双重调度,我们必须能够在第二次调度中修改我们想要作为参数传递的类。
因此,我们将操作FooList ,而不是将ObjectList作为声明类型进行操作,其中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; } 

我们确实可以引入一个包含用户值的抽象generics类,并提供一个访问器:

 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; } } 

现在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()方法以在List声明中为Foo类型声明通配符类型( ? )。

 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); } } 

实际上,如果确实需要,我们可以通过引入Java代码生成来简化进一步的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 { } 

其中Foo是在编译时处理的自定义注释。