Betrachten Sie dieses Beispiel (typisch für OOP-Bücher):
Ich habe eine Animal
Klasse, in der jeder Animal
viele Freunde haben kann.
Und Subklassen mögen Dog
, Duck
, Mouse
usw. , die wie ein bestimmtes Verhalten hinzufügen bark()
, quack()
usw.
Hier ist die Animal
Klasse:
public class Animal {
private Map<String,Animal> friends = new HashMap<>();
public void addFriend(String name, Animal animal){
friends.put(name,animal);
}
public Animal callFriend(String name){
return friends.get(name);
}
}
Und hier ist ein Code-Snippet mit vielen Typografien:
Mouse jerry = new Mouse();
jerry.addFriend("spike", new Dog());
jerry.addFriend("quacker", new Duck());
((Dog) jerry.callFriend("spike")).bark();
((Duck) jerry.callFriend("quacker")).quack();
Gibt es eine Möglichkeit, Generika für den Rückgabetyp zu verwenden, um die Typumwandlung zu entfernen, so dass ich sagen kann
jerry.callFriend("spike").bark();
jerry.callFriend("quacker").quack();
Hier ist ein Anfangscode mit dem Rückgabetyp, der als Parameter an die Methode übermittelt wird und nie verwendet wird.
public<T extends Animal> T callFriend(String name, T unusedTypeObj){
return (T)friends.get(name);
}
Gibt es eine Möglichkeit, den Rückgabetyp zur Laufzeit ohne den zusätzlichen Parameter zu ermitteln instanceof
? Oder zumindest durch Übergeben einer Klasse des Typs anstelle einer Dummy-Instanz.
Ich verstehe, dass Generika für die Überprüfung der Kompilierungszeit verwendet werden, aber gibt es dafür eine Problemumgehung?
quelle
Nein. Der Compiler kann nicht wissen, welcher Typ zurückkehren
jerry.callFriend("spike")
würde. Außerdem verbirgt Ihre Implementierung nur die Besetzung in der Methode ohne zusätzliche Typensicherheit. Bedenken Sie:In diesem speziellen Fall
talk()
wäre es viel besser , eine abstrakte Methode zu erstellen und sie in den Unterklassen entsprechend zu überschreiben:quelle
Sie könnten es so implementieren:
(Ja, dies ist ein gesetzlicher Code. Siehe Java Generics: Generischer Typ, der nur als Rückgabetyp definiert ist .)
Der Rückgabetyp wird vom Anrufer abgeleitet. Beachten Sie jedoch die
@SuppressWarnings
Anmerkung: Diese besagt, dass dieser Code nicht typsicher ist . Sie müssen es selbst überprüfen, sonst könnten SieClassCastExceptions
zur Laufzeit bekommen.Leider ist die Art und Weise, wie Sie es verwenden (ohne den Rückgabewert einer temporären Variablen zuzuweisen), die einzige Möglichkeit, den Compiler glücklich zu machen, es folgendermaßen aufzurufen:
Das ist vielleicht ein bisschen schöner als das Casting, aber Sie sind wahrscheinlich besser dran, der
Animal
Klasse eine abstraktetalk()
Methode zu geben, wie David Schmitt sagte.quelle
jerry.CallFriend<Dog>(...
sieht es meiner Meinung nach besser aus.java.util.Collections.emptyList()
Funktion der JRE genau so implementiert ist und sich ihr Javadoc als typsicher bewirbt.Diese Frage ist Punkt 29 in Effective Java sehr ähnlich - "Betrachten Sie typsichere heterogene Container." Laz 'Antwort kommt Blochs Lösung am nächsten. Sowohl put als auch get sollten jedoch aus Sicherheitsgründen das Class-Literal verwenden. Die Unterschriften würden werden:
Bei beiden Methoden sollten Sie überprüfen, ob die Parameter korrekt sind. Weitere Informationen finden Sie unter Effektives Java und das Class Javadoc.
quelle
Außerdem können Sie die Methode auf diese Weise auffordern, den Wert in einem bestimmten Typ zurückzugeben
Weitere Beispiele finden Sie hier in der Oracle Java-Dokumentation
quelle
Hier ist die einfachere Version:
Voll funktionsfähiger Code:
quelle
Casting to T not needed in this case but it's a good practice to do
. Ich meine, wenn es zur Laufzeit richtig gepflegt wird, was bedeutet "gute Praxis"?Wie Sie sagten, wäre es in Ordnung, eine Klasse zu bestehen, könnten Sie Folgendes schreiben:
Und dann benutze es so:
Nicht perfekt, aber so weit wie mit Java-Generika. Es gibt eine Möglichkeit, typisichere heterogene Container (THC) mithilfe von Super Type Tokens zu implementieren , aber das hat wieder seine eigenen Probleme.
quelle
Basierend auf der gleichen Idee wie bei Super Type Tokens können Sie eine typisierte ID erstellen, die anstelle einer Zeichenfolge verwendet wird:
Aber ich denke, dies kann den Zweck zunichte machen, da Sie jetzt neue ID-Objekte für jede Zeichenfolge erstellen und an ihnen festhalten müssen (oder sie mit den richtigen Typinformationen rekonstruieren müssen).
Aber Sie können die Klasse jetzt so verwenden, wie Sie es ursprünglich wollten, ohne die Casts.
Dadurch wird nur der Typparameter in der ID ausgeblendet. Dies bedeutet jedoch, dass Sie den Typ später aus dem Bezeichner abrufen können, wenn Sie dies wünschen.
Sie müssten auch die Vergleichs- und Hashing-Methoden von TypedID implementieren, wenn Sie zwei identische Instanzen einer ID vergleichen möchten.
quelle
"Gibt es eine Möglichkeit, den Rückgabetyp zur Laufzeit ohne den zusätzlichen Parameter mithilfe von instanceof zu ermitteln?"
Als alternative Lösung können Sie das Besuchermuster wie folgt verwenden . Machen Sie Animal abstrakt und implementieren Sie es Visitable:
Visitable bedeutet nur, dass eine Animal-Implementierung bereit ist, einen Besucher zu akzeptieren:
Und eine Besucherimplementierung kann alle Unterklassen eines Tieres besuchen:
So würde beispielsweise eine Dog-Implementierung folgendermaßen aussehen:
Der Trick dabei ist, dass der Hund, da er weiß, um welchen Typ es sich handelt, die relevante überladene Besuchsmethode des Besuchers v auslösen kann, indem er "this" als Parameter übergibt. Andere Unterklassen würden accept () genauso implementieren.
Die Klasse, die unterklassenspezifische Methoden aufrufen möchte, muss dann die Besucherschnittstelle wie folgt implementieren:
Ich weiß, dass es viel mehr Schnittstellen und Methoden gibt, als Sie erwartet hatten, aber es ist eine Standardmethode, um jeden bestimmten Subtyp mit genau null Instanzen von Prüfungen und null Typumwandlungen in den Griff zu bekommen. Und alles wird in einer standardsprachunabhängigen Weise durchgeführt, sodass es nicht nur für Java ist, sondern jede OO-Sprache gleich funktionieren sollte.
quelle
Nicht möglich. Wie soll die Karte wissen, welche Unterklasse von Tier sie erhalten wird, wenn nur ein String-Schlüssel angegeben wird?
Dies wäre nur möglich, wenn jedes Tier nur einen Freundtyp akzeptiert (dann könnte es sich um einen Parameter der Animal-Klasse handeln) oder wenn die Methode callFriend () einen Typparameter erhält. Aber es sieht wirklich so aus, als ob Sie den Punkt der Vererbung verpassen: Sie können Unterklassen nur dann einheitlich behandeln, wenn Sie ausschließlich die Methoden der Oberklasse verwenden.
quelle
Ich habe einen Artikel geschrieben, der einen Proof of Concept, Support-Klassen und eine Testklasse enthält, die zeigt, wie Super Type Tokens von Ihren Klassen zur Laufzeit abgerufen werden können. Kurz gesagt, Sie können abhängig von den tatsächlichen generischen Parametern, die vom Aufrufer übergeben werden, an alternative Implementierungen delegieren. Beispiel:
TimeSeries<Double>
delegiert an eine private innere Klasse, die verwendetdouble[]
TimeSeries<OHLC>
delegiert an eine private innere Klasse, die verwendetArrayList<OHLC>
Siehe: Verwenden von TypeTokens zum Abrufen generischer Parameter
Vielen Dank
Richard Gomes - Blog
quelle
Hier gibt es viele gute Antworten, aber dies ist der Ansatz, den ich für einen Appium-Test gewählt habe, bei dem das Einwirken auf ein einzelnes Element dazu führen kann, dass je nach den Einstellungen des Benutzers unterschiedliche Anwendungsstatus aufgerufen werden. Obwohl es nicht den Konventionen des OP-Beispiels entspricht, hoffe ich, dass es jemandem hilft.
Wenn Sie die Fehler nicht werfen möchten, können Sie sie wie folgt abfangen:
quelle
Nicht wirklich, denn wie Sie sagen, weiß der Compiler nur, dass callFriend () ein Tier zurückgibt, keinen Hund oder eine Ente.
Können Sie Animal keine abstrakte makeNoise () -Methode hinzufügen, die von ihren Unterklassen als Rinde oder Quacksalber implementiert wird?
quelle
Was Sie hier suchen, ist Abstraktion. Code gegen Schnittstellen mehr und Sie sollten weniger Casting machen müssen.
Das folgende Beispiel ist in C #, aber das Konzept bleibt das gleiche.
quelle
In meinem lib kontraktor habe ich folgendes gemacht:
Unterklasse:
Zumindest funktioniert dies innerhalb der aktuellen Klasse und wenn eine stark typisierte Referenz vorhanden ist. Mehrfachvererbung funktioniert, wird dann aber sehr knifflig :)
quelle
Ich weiß, dass dies eine ganz andere Sache ist, die der eine gefragt hat. Eine andere Möglichkeit, dies zu lösen, wäre die Reflexion. Ich meine, dies nutzt nicht die Vorteile von Generika, aber Sie können auf irgendeine Weise das Verhalten emulieren, das Sie ausführen möchten (einen Hund bellen lassen, einen Entenquacksalber machen usw.), ohne sich um das Typgießen zu kümmern:
quelle
wie wäre es mit
}}
quelle
Es gibt einen anderen Ansatz: Sie können den Rückgabetyp eingrenzen, wenn Sie eine Methode überschreiben. In jeder Unterklasse müssten Sie callFriend überschreiben, um diese Unterklasse zurückzugeben. Die Kosten wären die mehrfachen Deklarationen von callFriend, aber Sie könnten die gemeinsamen Teile auf eine intern aufgerufene Methode isolieren. Dies scheint mir viel einfacher zu sein als die oben genannten Lösungen und erfordert kein zusätzliches Argument, um den Rückgabetyp zu bestimmen.
quelle
public int getValue(String name){}
ist auspublic boolean getValue(String name){}
Sicht des Compilers nicht zu unterscheiden. Sie müssten entweder den Parametertyp ändern oder Parameter hinzufügen / entfernen, damit die Überlastung erkannt wird. Vielleicht verstehe ich dich nur falsch ...Sie können jeden Typ zurückgeben und direkt wie erhalten. Sie müssen nicht typisieren.
Dies ist am besten geeignet, wenn Sie andere Datensätze anstelle von echten Cursorn anpassen möchten.
quelle
(Cursor) cursor
zum Beispiel.cursor
seinCursor
und gibt immer aPerson
(oder null) zurück. Durch die Verwendung von Generika wird die Überprüfung dieser Einschränkungen aufgehoben, wodurch der Code unsicher wird.