
Sathish
Betrachten Sie dieses Beispiel (typisch in OOP-Büchern):
ich habe ein Animal
Klasse, wo jeder Animal
kann viele Freunde haben.
Und Unterklassen wie Dog
, Duck
, Mouse
usw., die ein bestimmtes Verhalten wie 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 viel Typumwandlung:
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 loszuwerden, damit ich sagen kann
jerry.callFriend("spike").bark();
jerry.callFriend("quacker").quack();
Hier ist ein anfänglicher Code mit Rückgabetyp, der als Parameter an die Methode übermittelt wird, der 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 herauszufinden, ohne den zusätzlichen Parameter zu verwenden instanceof
? Oder zumindest durch Übergeben einer Klasse des Typs anstelle einer Dummy-Instanz.
Ich verstehe, dass Generika für die Typüberprüfung zur Kompilierzeit gedacht sind, aber gibt es eine Problemumgehung dafür?

faul
Du könntest definieren callFriend
Hier entlang:
public <T extends Animal> T callFriend(String name, Class<T> type) {
return type.cast(friends.get(name));
}
Nennen Sie es dann so:
jerry.callFriend("spike", Dog.class).bark();
jerry.callFriend("quacker", Duck.class).quack();
Dieser Code hat den Vorteil, dass keine Compiler-Warnungen generiert werden. Natürlich ist dies wirklich nur eine aktualisierte Version des Castings aus den Tagen vor dem Generikum und bringt keine zusätzliche Sicherheit.

David Schmidt
Nein. Der Compiler kann den Typ nicht kennen jerry.callFriend("spike")
würden zurückkehren. Außerdem verbirgt Ihre Implementierung nur die Umwandlung in der Methode ohne zusätzliche Typsicherheit. Bedenken Sie:
jerry.addFriend("quaker", new Duck());
jerry.callFriend("quaker", /* unused */ new Dog()); // dies with illegal cast
In diesem speziellen Fall das Erstellen eines Abstracts talk()
-Methode zu verwenden und sie in den Unterklassen entsprechend zu überschreiben, würde Ihnen viel besser dienen:
Mouse jerry = new Mouse();
jerry.addFriend("spike", new Dog());
jerry.addFriend("quacker", new Duck());
jerry.callFriend("spike").talk();
jerry.callFriend("quacker").talk();

Michael myers
Du könntest es so umsetzen:
@SuppressWarnings("unchecked")
public <T extends Animal> T callFriend(String name) {
return (T)friends.get(name);
}
(Ja, dies ist legaler Code; siehe Java Generics: Generic type defined as return type only.)
Der Rückgabetyp wird vom Aufrufer abgeleitet. Beachten Sie jedoch die @SuppressWarnings
Anmerkung: Das sagt Ihnen das Dieser Code ist nicht typsicher. Sie müssen es selbst überprüfen, oder Sie könnten es bekommen ClassCastExceptions
zur Laufzeit.
So wie Sie es verwenden (ohne den Rückgabewert einer temporären Variablen zuzuweisen), besteht die einzige Möglichkeit, den Compiler glücklich zu machen, leider darin, es so aufzurufen:
jerry.<Dog>callFriend("spike").bark();
Dies mag zwar ein wenig netter sein als das Casting, aber Sie sind wahrscheinlich besser dran, das zu geben Animal
Klasse ein Abstract talk()
Methode, wie David Schmitt sagte.

Webjockey
Hier die einfachere Variante:
public <T> T callFriend(String name) {
return (T) friends.get(name); //Casting to T not needed in this case but its a good practice to do
}
Voll funktionsfähiger Code:
public class Test {
public static class Animal {
private Map<String,Animal> friends = new HashMap<>();
public void addFriend(String name, Animal animal){
friends.put(name,animal);
}
public <T> T callFriend(String name){
return (T) friends.get(name);
}
}
public static class Dog extends Animal {
public void bark() {
System.out.println("i am dog");
}
}
public static class Duck extends Animal {
public void quack() {
System.out.println("i am duck");
}
}
public static void main(String [] args) {
Animal animals = new Animal();
animals.addFriend("dog", new Dog());
animals.addFriend("duck", new Duck());
Dog dog = animals.callFriend("dog");
dog.bark();
Duck duck = animals.callFriend("duck");
duck.quack();
}
}
Außerdem können Sie die Methode auf diese Weise auffordern, den Wert in einem bestimmten Typ zurückzugeben
<T> T methodName(Class<T> var);
Mehr Beispiele Hier in der Oracle Java-Dokumentation

Fabian Steg
Da Sie gesagt haben, dass das Bestehen einer Klasse in Ordnung wäre, könnten Sie Folgendes schreiben:
public <T extends Animal> T callFriend(String name, Class<T> clazz) {
return (T) friends.get(name);
}
Und dann benutze es so:
jerry.callFriend("spike", Dog.class).bark();
jerry.callFriend("quacker", Duck.class).quack();
Nicht perfekt, aber das ist so ziemlich alles, was Sie mit Java-Generika erreichen können. Es gibt einen Weg zur Umsetzung Typesafe Heterogenous Containers (THC) mit Super Type Tokensaber das hat wieder seine eigenen Probleme.

Kothar
Basierend auf der gleichen Idee wie Super Type Tokens könnten Sie eine typisierte ID erstellen, die anstelle einer Zeichenfolge verwendet wird:
public abstract class TypedID<T extends Animal> {
public final Type type;
public final String id;
protected TypedID(String id) {
this.id = id;
Type superclass = getClass().getGenericSuperclass();
if (superclass instanceof Class) {
throw new RuntimeException("Missing type parameter.");
}
this.type = ((ParameterizedType) superclass).getActualTypeArguments()[0];
}
}
Aber ich denke, dass dies den Zweck vereiteln kann, 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).
Mouse jerry = new Mouse();
TypedID<Dog> spike = new TypedID<Dog>("spike") {};
TypedID<Duck> quacker = new TypedID<Duck>("quacker") {};
jerry.addFriend(spike, new Dog());
jerry.addFriend(quacker, new Duck());
Aber Sie können die Klasse jetzt so verwenden, wie Sie es ursprünglich wollten, ohne die Umwandlungen.
jerry.callFriend(spike).bark();
jerry.callFriend(quacker).quack();
Dies verbirgt lediglich den Typparameter innerhalb der ID, obwohl es bedeutet, dass Sie den Typ später aus dem Bezeichner abrufen können, wenn Sie dies wünschen.
Sie müssen auch die Vergleichs- und Hashing-Methoden von TypedID implementieren, wenn Sie zwei identische Instanzen einer ID vergleichen möchten.
9167700cookie-checkWie mache ich den Rückgabetyp der Methode generisch?yes