web-dev-qa-db-ja.com

Javaおよび多重ディスパッチでのオーバーロード

文字列値と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?!?!?
    }
}

テストでは、引数の型は実行時ではなくコンパイル時に決定されるようです。何故ですか?

この質問は以下に関連しています:

ポリモーフィズムvsオーバーライドvsオーバーロード
できるだけ簡単にポリモーフィズムを説明するようにしてください

編集:

呼び出されるメソッドはコンパイル時に決定されます。 instanceof演算子の使用を回避するための回避策はありますか?

この投稿は、vooの回答を秒単位で示し、遅延バインディングに関する詳細/代替手段を提供します。

一般的なJVMはシングルディスパッチのみを使用します。ランタイムタイプはレシーバーオブジェクトに対してのみ考慮されます。メソッドのパラメーターについては、静的タイプが考慮されます。 メソッドテーブル (C++の仮想テーブルに似ています)を使用すると、最適化による効率的な実装が非常に簡単になります。あなたは詳細を見つけることができます。 HotSpot Wiki で。

パラメータに多重ディスパッチが必要な場合は、以下を参照してください。

  • groovy 。しかし、私の最新の知識では、それは時代遅れで遅い多重ディスパッチの実装を持っています(例えば このパフォーマンス比較 を参照)、例えばキャッシュなし。
  • clojure ですが、それはJavaとはかなり異なります。
  • MultiJava 、Javaの多重ディスパッチを提供します。さらに、を使用できます
    • this.resend(...)の代わりにsuper(...)を使用して、囲んでいるメソッドの最も具体的なオーバーライドされたメソッドを呼び出します。
    • 値のディスパッチ(以下のコード例)。

Javaを使い続けたい場合は、次のことができます。

  • オーバーロードされたメソッドをより細かいクラス階層に移動して、アプリケーションを再設計します。例を Josh Blochの効果的な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'
  }
}
19
DaveFar

あなたが欲しいのは2倍以上一般的です 多重ディスパッチ 、実際には他の言語で実装されているものです(一般的なLISPが思い浮かびます)

おそらく主な理由Javaにはありません。これは、コンパイル時ではなく実行時にオーバーロードの解決を行う必要があるため、パフォーマンスが低下するためです。これを回避する通常の方法は- ビジターパターン -かなり醜いですが、そうです。

12
Voo

古い質問ですが答えがないため、Javaで、問題をクリーンな方法で解決するための具体的な解決策が提供されます。
実際、簡単ではありませんが、非常に興味深い質問です。これが私の貢献です。

呼び出されるメソッドはコンパイル時に決定されます。 instanceof演算子の使用を回避するための回避策はありますか?

優れた@DaveFarの回答で述べたように、Javaはシングルディスパッチメソッドのみをサポートします。
このディスパッチモードでは、コンパイラは、実行時型ではなく、宣言された型のパラメータに依存することにより、コンパイル後すぐに呼び出すメソッドを制限します。

文字列値と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()メソッドを呼び出す方法は?
ダブルディスパッチ実装の2番目のディスパッチは、訪問を受け入れるクラスの「this」コンテキストに依存しています。
したがって、この2番目のディスパッチを実行するには、IntegerString、およびObjectクラスにaccept()メソッドが必要です。

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

しかし不可能です!訪問したクラスは組み込みクラスです:StringIntegerObject
したがって、このメソッドを追加する方法はありません。
そしてとにかく、それを追加したくありません。

したがって、ダブルディスパッチを実装するには、2番目のディスパッチでパラメータとして渡すクラスを変更できる必要があります。
したがって、宣言された型として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 = a String

string = another String

整数= 1

object = 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サブレーザーの宣言が軽くなりました:

_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>_)を宣言するように変更する必要があります。

_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は、コンパイル時に処理されるカスタムアノテーションです。

3
davidxxx

オーバーロードされたメソッドを呼び出す場合、Javaは、関数に渡される変数のタイプに基づいて最も制限の厳しいタイプを選択します。このタイプは使用しません。実際のインスタンス。

3
unholysampler

JavaはすべてObject /オブジェクトです(プリミティブ型を除く)。文字列と整数をオブジェクトとして格納し、proveメソッドを呼び出すとそれらがは引き続きオブジェクトと呼ばれます。instanceofキーワードを確認する必要があります。 このリンクを確認してください

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

これは多形性ではありません。メソッドをオーバーロードし、オブジェクトタイプのパラメーターで呼び出しただけです。

1
NimChimpsky