Java 中的重载和多分派

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/9759141/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-16 07:18:36  来源:igfitidea点击:

Overloading in Java and multiple dispatch

javaooppolymorphismoverloading

提问by Виталий Олегович

I have a collection (or list or array list) in which I want to put both String values and double values. I decided to make it a collection of objects and using overloading ond polymorphism, but I did something wrong.

我有一个集合(或列表或数组列表),我想在其中放置字符串值和双精度值。我决定让它成为一个对象集合并使用重载和多态,但我做错了。

I run a little test:

我运行了一个小测试:

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?!?!?
    }
}

In the test seems like the argument type is decided at compile time and not at runtime. Why is that?

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

This question is related to:

这个问题与:

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

多态 vs 覆盖 vs 重载
尝试尽可能简单地描述多态

EDIT:

编辑:

Ok the method to be called is decided at compile time. Is there a workaround to avoid using the instanceofoperator?

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

采纳答案by DaveFar

This post seconds voo's answer, and gives details about/alternatives to late binding.

这篇文章秒了 voo 的回答,并提供了有关延迟绑定的详细信息/替代方法。

General JVMs only use single dispatch: the runtime type is only considered for the receiver object; for the method's parameters, the static type is considered. An efficient implementation with optimizations is quite easy using method tables(which are similar to C++'s virtual tables). You can find details e.g. in the HotSpot Wiki.

一般JVMs只使用single dispatch:runtime类型只考虑receiver对象;对于方法的参数,考虑静态类型。使用方法表(类似于 C++ 的虚拟表)可以很容易地进行优化的有效实现。您可以在HotSpot Wiki 等中找到详细信息

If you want multiple dispatchfor your parameters, take a look at

如果您想为您的参数进行多次分派,请查看

  • groovy. But to my latest knowledge, that has an outdated, slow multiple dispatch implementation (see e.g. this performance comparison), e.g. without caching.
  • clojure, but that is quite different to Java.
  • MultiJava, which offers multiple dispatch for Java. Additionally, you can use
    • this.resend(...)instead of super(...)to invoke the most-specific overridden method of the enclosing method;
    • value dispatching (code example below).
  • 时髦的。但据我所知,它有一个过时的、缓慢的多分派实现(参见例如这个性能比较),例如没有缓存。
  • clojure,但这与 Java 完全不同。
  • MultiJava,它为 Java 提供多重分派。此外,您可以使用
    • this.resend(...)而不是super(...)调用封闭方法的最具体的覆盖方法;
    • 值调度(下面的代码示例)。

If you want to stick with Java, you can

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

  • redesign your application by moving overloaded methods over a finer grained class hierarchy. An example is given in Josh Bloch's Effective Java, Item 41 (Use overloading judiciously);
  • use some design patterns, such as Strategy, Visitor, Observer. These can often solve the same problems as multiple dispatch (i.e. in those situations you have trivial solutions for those patterns using multiple dispatch).
  • 通过在更细粒度的类层次结构上移动重载方法来重新设计您的应用程序。Josh Bloch 的 Effective Java第 41 条(谨慎使用重载)中给出了一个示例;
  • 使用一些设计模式,例如 Strategy、Visitor、Observer。这些通常可以解决与多分派相同的问题(即在那些情况下,您对使用多分派的那些模式有一些简单的解决方案)。


Value dispatching:

价值调度:

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

回答by NimChimpsky

this isn't polymoprhism, you've simply overloaded a method and called it with parameter of object type

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

回答by unholysampler

When calling a method that is overloaded, Java picks the most restrictive type based on the type of the variablepassed to the function. It does not use the type of the actual instance.

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

回答by nist

Everything in Java is an Object/object (except primitive types). You store strings and integers as objects, and then as you call the provemethod they are still referred to as objects. You should have a look at the instanceofkeyword. Check this link

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

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

回答by Voo

What you want is double or more general multiple dispatch, something that is actually implemented in other languages (common lisp comes to mind)

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

Presumably the main reason java doesn't have it, is because it comes at a performance penalty because overload resolution has to be done at runtime and not compile time. The usual way around this is the visitor pattern- pretty ugly, but that's how it is.

大概java没有它的主要原因是因为它会导致性能损失,因为必须在运行时而不是编译时完成重载解析。通常的方法是访问者模式- 非常丑陋,但事实就是如此。

回答by davidxxx

Old question but no answer provides a concrete solution in Java to solve the issue in a clean way.
In fact, not easy but very interesting question. Here is my contribution.

老问题但没有答案在 Java 中提供了一个具体的解决方案,以干净的方式解决这个问题。
事实上,这并不容易但很有趣的问题。这是我的贡献。

Ok the method to be called is decided at compile time. Is there a workaround to avoid using the instanceof operator?

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

As said in the excellent @DaveFar answer, Java supports only the single-dispatch method.
In this dispatching mode, the compiler bounds the method to invoke as soon as the compilation by relying on the declared types of the parameters and not their runtime types.

正如出色的@DaveFar 回答中所说,Java 仅支持单分派方法。
在这种调度模式下,编译器通过依赖于参数的声明类型而不是它们的运行时类型来限制方法在编译后立即调用。

I have a collection (or list or array list) in which I want to put both String values and double values.

我有一个集合(或列表或数组列表),我想在其中放置字符串值和双精度值。

To solve the answer in a clean way and use a double dispatch, we have to bring abstraction for the manipulated data.
Why ?

为了以干净的方式解决答案并使用双重调度,我们必须对被操纵的数据进行抽象。
为什么 ?

Here a naive visitor approach to illustrate the issue :

这是一个天真的访问者方法来说明这个问题:

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

}

Now, question : how visited classes may invoke the visit()method ?
The second dispatch of the double dispatch implementation relies on the "this" context of the class that accepts to be visited.
So we need to have a accept()method in Integer, Stringand Objectclasses to perform this second dispatch :

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

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

But impossible ! Visited classes are built-in classes : String, Integer, Object.
So we have no way to add this method.
And anyway, we don't want to add that.

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

So to implement the double dispatch, we have to be able to modify the classes that we want to pass as parameter in the second dispatch.
So instead of manipulating Objectand List<Object>as declared type, we will manipulate Fooand List<Foo>where the Fooclass is a wrapper holding the user value.

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

Here is the Foointerface :

这是Foo界面:

public interface Foo {
    void accept(DisplayVisitor v);
    Object getValue();
}

getValue()returns the user value.
It specifies Objectas return type but Java supports covariance returns (since the 1.5 version), so we could define a more specific type for each subclass to avoid downcasts.

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

ObjectFoo

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

}

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

}

Here is the DisplayVisitorclass visiting Foosubclasses :

这是访问子类的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());
    }

}

And here is a sample code to test the implementation :

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

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

Output :

输出 :

string=a String

string=another String

integer=1

object=100

字符串=一个字符串

字符串=另一个字符串

整数=1

对象=100



Improving the implementation

改进实施

The actual implementation requires the introduction of a specific wrapper class for each buit-in type we want to wrap. As discussed, we don't have the choice to operate a double dispatch.
But note that the repeated code in Foosubclasses could be avoided :

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

private Integer value; // or String or Object

@Override
public Object getValue() {
    return value;
}

We could indeed introduce a abstract generic class that holds the user value and provides an accessor to :

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

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

}

Now Foosublasses are lighter to declare :

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

}

And the test()method should be modified to declare a wildcard type (?) for the Footype in the List<Foo>declaration.

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

}

In fact, if really needed, we could simplify further Foosubclasses by introducing java code generation.

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

Declaring this subclass :

声明这个子类:

public class StringFoo extends Foo<String> {

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

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

}

could as simple as declaring a class and adding an annotation on:

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

@Foo(String.class)
public class StringFoo { }

Where Foois a custom annotation processed at compile time.

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