在Java中重载和多次调度

Вит*_*вич 25 java oop polymorphism overloading

我有一个集合(或列表或数组列表),我想在其中放置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?!?!?
    }
}
Run Code Online (Sandbox Code Playgroud)

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

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

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

编辑:

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

Dav*_*Far 19

这篇文章是秒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'
  }
}
Run Code Online (Sandbox Code Playgroud)


Voo*_*Voo 12

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

大概是java没有它的主要原因是因为它的性能损失是因为重载决策必须在运行时而不是编译时完成.通常的方法是访客模式 - 非常难看,但就是这样.


dav*_*xxx 6

老问题但没有答案在 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"));
    }

}
Run Code Online (Sandbox Code Playgroud)

现在,问题是:访问的类如何调用该visit()方法?
双分派实现的第二个分派依赖于接受被访问的类的“this”上下文。
因此,我们需要有一个accept()在方法IntegerStringObject类来执行本次调度:

public void accept(DisplayVisitor visitor){
    visitor.visit(this);
}
Run Code Online (Sandbox Code Playgroud)

但不可能!访问的类是内置类:String, Integer, Object
所以我们没有办法添加这个方法。
无论如何,我们不想添加它。

所以要实现双重调度,我们必须能够修改我们想要在第二次调度中作为参数传递的类。
因此,不是操作ObjectList<Object>声明类型,我们将操作FooList<Foo>其中Foo类是保存用户值的包装器。

这是Foo界面:

public interface Foo {
    void accept(DisplayVisitor v);
    Object getValue();
}
Run Code Online (Sandbox Code Playgroud)

getValue()返回用户值。
它指定Object为返回类型,但 Java 支持协方差返回(从 1.5 版本开始),因此我们可以为每个子类定义更具体的类型以避免向下转换。

对象Foo

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

}
Run Code Online (Sandbox Code Playgroud)

字符串富

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

}
Run Code Online (Sandbox Code Playgroud)

整数富

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

}
Run Code Online (Sandbox Code Playgroud)

这是访问子类的DisplayVisitorFoo

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

}
Run Code Online (Sandbox Code Playgroud)

这是测试实现的示例代码:

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();
    }
}
Run Code Online (Sandbox Code Playgroud)

输出 :

字符串=一个字符串

字符串=另一个字符串

整数=1

对象=100


改进实施

实际的实现需要为我们想要包装的每个内置类型引入一个特定的包装类。正如所讨论的,我们没有选择操作双重调度。
但请注意,可以避免Foo子类中的重复代码:

private Integer value; // or String or Object

@Override
public Object getValue() {
    return value;
}
Run Code Online (Sandbox Code Playgroud)

我们确实可以引入一个抽象泛型类来保存用户值并提供一个访问器:

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

}
Run Code Online (Sandbox Code Playgroud)

现在Foosublasses 声明更轻:

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

}
Run Code Online (Sandbox Code Playgroud)

test()方法应该被修改声明一个通配符类型(?为)Foo在类型List<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);
    }

}
Run Code Online (Sandbox Code Playgroud)

事实上,如果真的需要,我们可以Foo通过引入java代码生成来进一步简化子类。

声明这个子类:

public class StringFoo extends Foo<String> {

    public StringFoo(String string) {
        super(string);
    }

    @Override
    public void accept(DisplayVisitor v) {
        v.visit(this);
    }

}
Run Code Online (Sandbox Code Playgroud)

可以像声明一个类并在以下位置添加注释一样简单:

@Foo(String.class)
public class StringFoo { }
Run Code Online (Sandbox Code Playgroud)

Foo编译时处理的自定义注释在哪里。