文字列値と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 で。
パラメータに多重ディスパッチが必要な場合は、以下を参照してください。
this.resend(...)
の代わりにsuper(...)
を使用して、囲んでいるメソッドの最も具体的なオーバーライドされたメソッドを呼び出します。Javaを使い続けたい場合は、次のことができます。
バリューディスパッチ:
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'
}
}
古い質問ですが答えがないため、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番目のディスパッチを実行するには、Integer
、String
、およびObject
クラスにaccept()
メソッドが必要です。
_public void accept(DisplayVisitor visitor){
visitor.visit(this);
}
_
しかし不可能です!訪問したクラスは組み込みクラスです:String
、Integer
、Object
。
したがって、このメソッドを追加する方法はありません。
そしてとにかく、それを追加したくありません。
したがって、ダブルディスパッチを実装するには、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
は、コンパイル時に処理されるカスタムアノテーションです。
オーバーロードされたメソッドを呼び出す場合、Javaは、関数に渡される変数のタイプに基づいて最も制限の厳しいタイプを選択します。このタイプは使用しません。実際のインスタンス。
JavaはすべてObject
/オブジェクトです(プリミティブ型を除く)。文字列と整数をオブジェクトとして格納し、prove
メソッドを呼び出すとそれらがは引き続きオブジェクトと呼ばれます。instanceof
キーワードを確認する必要があります。 このリンクを確認してください
void prove(Object o){
if (o instanceof String)
System.out.println("String");
....
}
これは多形性ではありません。メソッドをオーバーロードし、オブジェクトタイプのパラメーターで呼び出しただけです。