在Java中重载和多次调度

问题描述 投票:23回答:6

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

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

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

Polymorphism vs Overriding vs Overloading Try to describe polymorphism as easy as you can

编辑:

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

java oop polymorphism overloading
6个回答
19
投票

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

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

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

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

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

  • 通过在更精细的类层次结构上移动重载方法来重新设计应用程序。 Josh Bloch's 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'
  }
}

12
投票

你想要的是双倍或更多的一般multiple dispatch,实际上用其他语言实现的东西(常见的lisp浮现在脑海中)

大概是java没有它的主要原因是因为它的性能损失是因为重载决策必须在运行时而不是编译时完成。通常的方法是visitor pattern - 非常丑陋,但就是这样。


3
投票

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


2
投票

旧问题,但没有答案提供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()IntegerString类中使用Object方法来执行第二次调度:

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

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

因此,为了实现双分派,我们必须能够在第二个分派中修改我们想要作为参数传递的类。 因此,我们将操纵ObjectList<Object>而不是操纵FooList<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;
    }

}

这是访问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<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 =另一个String

整数= 1

对象= 100


改进实施

实际的实现需要为我们想要包装的每个buit-in类型引入一个特定的包装类。如上所述,我们无法选择双重调度。 但请注意,可以避免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;
    }

}

现在qazxsw poi子类更轻松地声明:

Foo

并且应修改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()声明中为?类型声明通配符类型(Foo)。

List<Foo>

事实上,如果真的需要,我们可以通过引入java代码生成来简化进一步的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); } } 子类。

声明此子类:

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 { } 是在编译时处理的自定义注释。


1
投票

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


1
投票

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

Check this link
© www.soinside.com 2019 - 2024. All rights reserved.