Wie mache ich den Rückgabetyp der Methode generisch?

Lesezeit: 8 Minuten

Wie mache ich den Ruckgabetyp der Methode generisch
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?

Wie mache ich den Ruckgabetyp der Methode generisch
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.

  • … hat aber immer noch keine Kompilierzeit-Typüberprüfung zwischen den Parametern des callFriend()-Aufrufs.

    – David Schmidt

    16. Januar 2009 um 16:32 Uhr

  • Dies ist bisher die beste Antwort – aber Sie sollten addFriend auf die gleiche Weise ändern. Es erschwert das Schreiben von Fehlern, da Sie dieses Klassenliteral an beiden Stellen benötigen.

    – Craig P. Motlin

    16. Januar 2009 um 17:01 Uhr

  • @Jaider, nicht genau das gleiche, aber das wird funktionieren: // Animal Class public T CallFriend(string name) where T : Animal { return friends[name] als T; } // Aufrufende Klasse jerry.CallFriend(“spike”).Bark(); jerry.CallFriend(“quacker”).Quack();

    – Nestor Ledon

    21. Mai 2014 um 16:27 Uhr


1646260029 838 Wie mache ich den Ruckgabetyp der Methode generisch
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();

  • Obwohl die mmyers-Methode funktionieren mag, denke ich, dass diese Methode die bessere OO-Programmierung ist und Ihnen in Zukunft einige Probleme ersparen wird.

    – James McMahon

    16. Januar 2009 um 15:54 Uhr

  • Dies ist der richtige Weg, um das gleiche Ergebnis zu erzielen. Beachten Sie, dass das Ziel darin besteht, abgeleitetes klassenspezifisches Verhalten zur Laufzeit zu erhalten, ohne explizit selbst Code schreiben zu müssen, um die hässliche Typüberprüfung und Umwandlung durchzuführen. Die von @laz vorgeschlagene Methode funktioniert, wirft aber die Typsicherheit aus dem Fenster. Diese Methode erfordert weniger Codezeilen (da Methodenimplementierungen sowieso spät gebunden und zur Laufzeit nachgeschlagen werden), aber dennoch können Sie ganz einfach ein eindeutiges Verhalten für jede Unterklasse von Animal definieren.

    – dkuh

    8. Juli 2013 um 23:58 Uhr

  • Die ursprüngliche Frage betrifft jedoch nicht die Typsicherheit. So wie ich es lese, möchte der Fragesteller nur wissen, ob es eine Möglichkeit gibt, Generika zu nutzen, um zu vermeiden, dass sie gecastet werden müssen.

    – faul

    9. Juli 2013 um 3:46 Uhr


  • @laz: Ja, die ursprüngliche Frage – so wie sie gestellt wurde – betrifft nicht die Typsicherheit. Das ändert nichts an der Tatsache, dass es einen sicheren Weg gibt, dies zu implementieren und Klassenumwandlungsfehler zu eliminieren. Siehe auch weblogs.asp.net/alex_papadimoulis/archive/2005/05/25/…

    – David Schmidt

    9. Juli 2013 um 13:19 Uhr


  • Ich bin nicht anderer Meinung, aber wir haben es mit Java und all seinen Designentscheidungen/Schwächen zu tun. Ich sehe diese Frage nur als Versuch zu lernen, was in Java-Generika möglich ist, nicht als xyproblem (meta.stackexchange.com/questions/66377/what-is-the-xy-problem), die überarbeitet werden müssen. Wie bei jedem Muster oder Ansatz gibt es Zeiten, in denen der von mir bereitgestellte Code angemessen ist, und Zeiten, in denen etwas völlig anderes (wie das, was Sie in dieser Antwort vorgeschlagen haben) erforderlich ist.

    – faul

    9. Juli 2013 um 14:22 Uhr


1646260030 900 Wie mache ich den Ruckgabetyp der Methode generisch
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.

  • Methodenverkettung war nicht wirklich beabsichtigt. Es macht mir nichts aus, den Wert einer Subtyped-Variablen zuzuweisen und zu verwenden. Danke für die Lösung.

    – Satish

    16. Januar 2009 um 16:25 Uhr

  • Dies funktioniert perfekt beim Verketten von Methodenaufrufen!

    – Hartmut Pfarr

    16. Dezember 2015 um 21:03 Uhr

  • Ich mag diese Syntax sehr. Ich denke, in C# ist es jerry.CallFriend<Dog>(... was meiner Meinung nach besser aussieht.

    – andho

    7. September 2017 um 7:42 Uhr

  • Interessant, dass die JRE ihre eigene ist java.util.Collections.emptyList() Die Funktion ist genau so implementiert, und ihr Javadoc bewirbt sich selbst als typsicher.

    – Ti Strga

    7. Juni 2018 um 16:50 Uhr

  • @TiStrga: Es ist interessant! Der Grund Collections.emptyList() Damit kann man durchkommen, dass es per Definition einer leeren Liste kein Elementobjekt vom Typ gibt T. Es besteht also keine Gefahr, dass ein Objekt in den falschen Typ umgewandelt wird. Die Listenobjekte selbst können mit beliebigen Typen arbeiten, solange es keine Elemente gibt.

    – Lii

    17. November 2020 um 10:05 Uhr


1646260031 857 Wie mache ich den Ruckgabetyp der Methode generisch
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

Wie mache ich den Ruckgabetyp der Methode generisch
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.

1646260033 592 Wie mache ich den Ruckgabetyp der Methode generisch
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.

916770cookie-checkWie mache ich den Rückgabetyp der Methode generisch?

This website is using cookies to improve the user-friendliness. You agree by using the website further.

Privacy policy