Ich experimentiere mit diesem Code:
interface Callee {
public void foo(Object o);
public void foo(String s);
public void foo(Integer i);
}
class CalleeImpl implements Callee
public void foo(Object o) {
logger.debug("foo(Object o)");
}
public void foo(String s) {
logger.debug("foo(\"" + s + "\")");
}
public void foo(Integer i) {
logger.debug("foo(" + i + ")");
}
}
Callee callee = new CalleeImpl();
Object i = new Integer(12);
Object s = "foobar";
Object o = new Object();
callee.foo(i);
callee.foo(s);
callee.foo(o);
Dies wird foo(Object o)
dreimal gedruckt . Ich erwarte, dass bei der Methodenauswahl der reale (nicht der deklarierte) Parametertyp berücksichtigt wird. Vermisse ich etwas Gibt es eine Möglichkeit, diesen Code so zu ändern, dass er gedruckt wird foo(12)
, foo("foobar")
und foo(Object o)
?
foo(Object)
. Zur Laufzeit bestimmt die Klasse des Objekts , für das die Methode aufgerufen wird , welche Implementierung dieser Methode aufgerufen wird, wobei berücksichtigt wird, dass es sich möglicherweise um eine Instanz einer Unterklasse des deklarierten Typs handelt, die die Methode überschreibt.Wie bereits erwähnt, wird die Überladungsauflösung zur Kompilierungszeit durchgeführt.
Java Puzzlers hat dafür ein schönes Beispiel:
Puzzle 46: Der Fall des verwirrenden Konstruktors
Dieses Puzzle präsentiert Ihnen zwei verwirrende Konstruktoren. Die Hauptmethode ruft einen Konstruktor auf, aber welcher? Die Ausgabe des Programms hängt von der Antwort ab. Was druckt das Programm oder ist es überhaupt legal?
Lösung 46: Fall des verwirrenden Konstruktors
... Javas Überlastungsauflösungsprozess läuft in zwei Phasen ab. In der ersten Phase werden alle Methoden oder Konstruktoren ausgewählt, auf die zugegriffen werden kann und die anwendbar sind. In der zweiten Phase werden die spezifischsten Methoden oder Konstruktoren ausgewählt, die in der ersten Phase ausgewählt wurden. Eine Methode oder ein Konstruktor ist weniger spezifisch als eine andere, wenn sie Parameter akzeptieren kann, die an die andere übergeben wurden [JLS 15.12.2.5].
In unserem Programm sind beide Konstruktoren zugänglich und anwendbar. Der Konstruktor Confusing (Object) akzeptiert alle an Confusing (double []) übergebenen Parameter , sodass Confusing (Object) weniger spezifisch ist. (Jedes Doppelarray ist ein Objekt , aber nicht jedes Objekt ist ein Doppelarray .) Der spezifischste Konstruktor ist daher Confusing (double []) , was die Ausgabe des Programms erklärt.
Dieses Verhalten ist sinnvoll, wenn Sie einen Wert vom Typ double [] übergeben . Es ist nicht intuitiv, wenn Sie null übergeben . Der Schlüssel zum Verständnis dieses Puzzles besteht darin, dass der Test, für den die Methode oder der Konstruktor am spezifischsten ist, nicht die tatsächlichen Parameter verwendet : die im Aufruf angezeigten Parameter. Sie werden nur verwendet, um zu bestimmen, welche Überladungen anwendbar sind. Sobald der Compiler bestimmt hat, welche Überladungen anwendbar und zugänglich sind, wählt er die spezifischste Überladung aus, wobei nur die formalen Parameter verwendet werden: die in der Deklaration angezeigten Parameter.
Um den Konstruktor Confusing (Object) mit einem Nullparameter aufzurufen , schreiben Sie new Confusing ((Object) null) . Dies stellt sicher, dass nur Verwirrend (Objekt) anwendbar ist. Um den Compiler zu zwingen, eine bestimmte Überladung auszuwählen, werden die tatsächlichen Parameter allgemeiner in die deklarierten Typen der formalen Parameter umgewandelt.
quelle
Die Möglichkeit, einen Aufruf an eine Methode zu senden, die auf Argumenttypen basiert, wird als Mehrfachversand bezeichnet . In Java erfolgt dies mit dem Besuchermuster .
Da es sich jedoch um
Integer
s undString
s handelt, können Sie dieses Muster nicht einfach einbinden (Sie können diese Klassen einfach nicht ändern). Somit ist ein Rieseswitch
mit Objektlaufzeit die Waffe Ihrer Wahl.quelle
In Java wird die aufzurufende Methode (wie die zu verwendende Methodensignatur) zur Kompilierungszeit festgelegt, sodass sie zum Typ der Kompilierungszeit gehört.
Das typische Muster, um dies zu umgehen, besteht darin, den Objekttyp in der Methode mit der Objektsignatur zu überprüfen und mit einer Umwandlung an die Methode zu delegieren.
Wenn Sie viele Typen haben und dies nicht verwaltbar ist, ist das Überladen von Methoden wahrscheinlich nicht der richtige Ansatz. Vielmehr sollte die öffentliche Methode nur Object verwenden und eine Art Strategiemuster implementieren, um die entsprechende Behandlung pro Objekttyp zu delegieren.
quelle
Ich hatte ein ähnliches Problem beim Aufrufen des richtigen Konstruktors einer Klasse namens "Parameter", die mehrere grundlegende Java-Typen wie String, Integer, Boolean, Long usw. annehmen kann. Angesichts eines Arrays von Objekten möchte ich sie in ein Array konvertieren meiner Parameterobjekte durch Aufrufen des spezifischsten Konstruktors für jedes Objekt im Eingabearray. Ich wollte auch den Konstruktorparameter (Objekt o) definieren, der eine IllegalArgumentException auslösen würde. Ich habe natürlich festgestellt, dass diese Methode für jedes Objekt in meinem Array aufgerufen wird.
Die Lösung, die ich benutzte, bestand darin, den Konstruktor durch Reflexion nachzuschlagen ...
Keine hässliche Instanz, Schalteranweisungen oder Besuchermuster erforderlich! :) :)
quelle
Java untersucht den Referenztyp, wenn versucht wird, die aufzurufende Methode zu bestimmen. Wenn Sie Ihren Code erzwingen möchten, wählen Sie die richtige Methode. Sie können Ihre Felder als Instanzen des bestimmten Typs deklarieren:
Sie können Ihre Parameter auch als Typ des Parameters umwandeln:
quelle
Wenn es eine genaue Übereinstimmung zwischen der Anzahl und den Arten von Argumenten gibt, die im Methodenaufruf angegeben sind, und der Methodensignatur einer überladenen Methode, wird diese Methode aufgerufen. Sie verwenden Objektreferenzen, daher entscheidet Java beim Kompilieren, dass es für Object param eine Methode gibt, die Object direkt akzeptiert. Also hat es diese Methode dreimal aufgerufen.
quelle