Wie mache ich den Rückgabetyp der Methode generisch?

588

Betrachten Sie dieses Beispiel (typisch für OOP-Bücher):

Ich habe eine AnimalKlasse, in der jeder Animalviele Freunde haben kann.
Und Subklassen mögen Dog, Duck, Mouseusw. , die wie ein bestimmtes Verhalten hinzufügen bark(), quack()usw.

Hier ist die AnimalKlasse:

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?

Sathish
quelle

Antworten:

903

Sie können Folgendes definieren callFriend:

public <T extends Animal> T callFriend(String name, Class<T> type) {
    return type.cast(friends.get(name));
}

Dann nenne es 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 nur eine aktualisierte Version des Castings aus den vorgenerischen Tagen und bietet keine zusätzliche Sicherheit.

laz
quelle
29
... hat aber noch keine Überprüfung der Kompilierungszeit zwischen den Parametern des Aufrufs callFriend ().
David Schmitt
2
Dies ist die bisher beste Antwort - aber Sie sollten addFriend auf die gleiche Weise ändern. Es macht es schwieriger, Fehler zu schreiben, da Sie dieses Klassenliteral an beiden Stellen benötigen.
Craig P. Motlin
@Jaider, nicht genau das gleiche, aber das wird funktionieren: // Animal Class public T CallFriend <T> (Stringname) wobei T: Animal {Freunde [Name] als T zurückgeben; } // Klasse jerry.CallFriend <Dog> ("spike") aufrufen. Bark (); jerry.CallFriend <Duck> ("Quacker"). Quack ();
Nestor Ledon
124

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:

jerry.addFriend("quaker", new Duck());
jerry.callFriend("quaker", /* unused */ new Dog()); // dies with illegal cast

In diesem speziellen Fall talk()wäre es viel besser , eine abstrakte Methode zu erstellen und sie in den Unterklassen entsprechend zu überschreiben:

Mouse jerry = new Mouse();
jerry.addFriend("spike", new Dog());
jerry.addFriend("quacker", new Duck());

jerry.callFriend("spike").talk();
jerry.callFriend("quacker").talk();
David Schmitt
quelle
10
Während die mmyers-Methode möglicherweise funktioniert, ist diese Methode meiner Meinung nach eine bessere OO-Programmierung und erspart Ihnen in Zukunft einige Probleme.
James McMahon
1
Dies ist der richtige Weg, um das gleiche Ergebnis zu erzielen. Beachten Sie, dass das Ziel darin besteht, zur Laufzeit ein abgeleitetes klassenspezifisches Verhalten zu erhalten, ohne selbst explizit Code zu schreiben, um die hässliche Typprüfung und das Casting durchzuführen. Die von @laz vorgeschlagene Methode funktioniert, wirft jedoch die Typensicherheit aus dem Fenster. Diese Methode erfordert weniger Codezeilen (da Methodenimplementierungen ohnehin zu spät gebunden sind und zur Laufzeit nachgeschlagen werden). Sie können jedoch problemlos ein eindeutiges Verhalten für jede Unterklasse von Animal definieren.
dcow
Die ursprüngliche Frage bezieht sich jedoch nicht auf die Typensicherheit. So wie ich es lese, möchte der Fragesteller nur wissen, ob es eine Möglichkeit gibt, Generika zu nutzen, um ein Casting zu vermeiden.
Laz
2
@laz: Ja, die ursprüngliche Frage - wie gestellt - betrifft nicht die Typensicherheit. Das ändert nichts an der Tatsache, dass es einen sicheren Weg gibt, dies zu implementieren und Klassenbesetzungsfehler zu eliminieren. Siehe auch weblogs.asp.net/alex_papadimoulis/archive/2005/05/25/…
David Schmitt
3
Darüber bin ich nicht anderer Meinung, aber wir beschäftigen uns mit Java und all seinen Designentscheidungen / Schwächen. Ich sehe diese Frage nur als Versuch zu lernen, was in Java-Generika möglich ist, und nicht als xyproblem ( meta.stackexchange.com/questions/66377/what-is-the-xy-problem ), das überarbeitet werden muss. 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 erforderlich ist (wie das, was Sie in dieser Antwort vorgeschlagen haben).
Laz
114

Sie könnten es so implementieren:

@SuppressWarnings("unchecked")
public <T extends Animal> T callFriend(String name) {
    return (T)friends.get(name);
}

(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 @SuppressWarningsAnmerkung: Diese besagt, dass dieser Code nicht typsicher ist . Sie müssen es selbst überprüfen, sonst könnten Sie ClassCastExceptionszur 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:

jerry.<Dog>callFriend("spike").bark();

Das ist vielleicht ein bisschen schöner als das Casting, aber Sie sind wahrscheinlich besser dran, der AnimalKlasse eine abstrakte talk()Methode zu geben, wie David Schmitt sagte.

Michael Myers
quelle
Methodenverkettung war eigentlich keine Absicht. Es macht mir nichts aus, den Wert einer subtypisierten Variablen zuzuweisen und ihn zu verwenden. Danke für die Lösung.
Sathish
Dies funktioniert perfekt bei der Verkettung von Methodenaufrufen!
Hartmut P.
Diese Syntax gefällt mir sehr gut. Ich denke, in C # jerry.CallFriend<Dog>(...sieht es meiner Meinung nach besser aus.
Andho
Interessant, dass die eigene java.util.Collections.emptyList()Funktion der JRE genau so implementiert ist und sich ihr Javadoc als typsicher bewirbt.
Ti Strga
31

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:

public <T extends Animal> void addFriend(String name, Class<T> type, T animal);
public <T extends Animal> T callFriend(String name, Class<T> type);

Bei beiden Methoden sollten Sie überprüfen, ob die Parameter korrekt sind. Weitere Informationen finden Sie unter Effektives Java und das Class Javadoc.

Craig P. Motlin
quelle
17

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);

Weitere Beispiele finden Sie hier in der Oracle Java-Dokumentation

Isuru Chathuranga
quelle
17

Hier ist die einfachere Version:

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();

        }
    }
Webjockey
quelle
1
Was macht 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"?
Farid
Ich wollte damit sagen, dass explizites Casting (T) nicht erforderlich ist, da die Rückgabetypdeklaration <T> in der Methodendeklaration ausreichend sein sollte
Webjockey
9

Wie Sie sagten, wäre es in Ordnung, eine Klasse zu bestehen, 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 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.

Fabian Steeg
quelle
Es tut mir leid, aber das ist genau die gleiche Antwort, die Laz hat. Sie kopieren ihn entweder oder er kopiert Sie.
James McMahon
Das ist eine gute Möglichkeit, den Typ zu übergeben. Aber es ist immer noch nicht sicher, wie Schmitt sagte. Ich könnte noch eine andere Klasse bestehen und die Typografie wird bombardieren. mmyers 2. Antwort, um den Typ in Return festzulegen, scheint besser zu sein
Sathish
4
Nemo, wenn Sie die Veröffentlichungszeit überprüfen, werden Sie sehen, dass wir sie ziemlich genau im selben Moment veröffentlicht haben. Sie sind auch nicht genau gleich, nur zwei Zeilen.
Fabian Steeg
@Fabian Ich habe eine ähnliche Antwort gepostet, aber es gibt einen wichtigen Unterschied zwischen Blochs Folien und dem, was in Effective Java veröffentlicht wurde. Er verwendet Klasse <T> anstelle von TypeRef <T>. Aber das ist immer noch eine gute Antwort.
Craig P. Motlin
8

Basierend auf der gleichen Idee wie bei Super Type Tokens können 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, 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).

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 Casts.

jerry.callFriend(spike).bark();
jerry.callFriend(quacker).quack();

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.

Mike Houston
quelle
8

"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:

abstract public class Animal implements Visitable {
  private Map<String,Animal> friends = new HashMap<String,Animal>();

  public void addFriend(String name, Animal animal){
      friends.put(name,animal);
  }

  public Animal callFriend(String name){
      return friends.get(name);
  }
}

Visitable bedeutet nur, dass eine Animal-Implementierung bereit ist, einen Besucher zu akzeptieren:

public interface Visitable {
    void accept(Visitor v);
}

Und eine Besucherimplementierung kann alle Unterklassen eines Tieres besuchen:

public interface Visitor {
    void visit(Dog d);
    void visit(Duck d);
    void visit(Mouse m);
}

So würde beispielsweise eine Dog-Implementierung folgendermaßen aussehen:

public class Dog extends Animal {
    public void bark() {}

    @Override
    public void accept(Visitor v) { v.visit(this); }
}

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:

public class Example implements Visitor {

    public void main() {
        Mouse jerry = new Mouse();
        jerry.addFriend("spike", new Dog());
        jerry.addFriend("quacker", new Duck());

        // Used to be: ((Dog) jerry.callFriend("spike")).bark();
        jerry.callFriend("spike").accept(this);

        // Used to be: ((Duck) jerry.callFriend("quacker")).quack();
        jerry.callFriend("quacker").accept(this);
    }

    // This would fire on callFriend("spike").accept(this)
    @Override
    public void visit(Dog d) { d.bark(); }

    // This would fire on callFriend("quacker").accept(this)
    @Override
    public void visit(Duck d) { d.quack(); }

    @Override
    public void visit(Mouse m) { m.squeak(); }
}

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.

Antti Siiskonen
quelle
6

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.

Michael Borgwardt
quelle
5

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 verwendet double[]
  • TimeSeries<OHLC> delegiert an eine private innere Klasse, die verwendet ArrayList<OHLC>

Siehe: Verwenden von TypeTokens zum Abrufen generischer Parameter

Vielen Dank

Richard Gomes - Blog

Richard Gomes
quelle
Vielen Dank, dass Sie uns Ihre Erkenntnisse mitgeteilt haben. Ihr Artikel erklärt wirklich alles!
Yann-Gaël Guéhéneuc
3

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.

public <T extends MobilePage> T tapSignInButton(Class<T> type) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {
    //signInButton.click();
    return type.getConstructor(AppiumDriver.class).newInstance(appiumDriver);
}
  • MobilePage ist die Superklasse, die der Typ erweitert, was bedeutet, dass Sie jedes seiner Kinder verwenden können (duh)
  • Mit type.getConstructor (Param.class usw.) können Sie mit dem Konstruktor des Typs interagieren. Dieser Konstruktor sollte für alle erwarteten Klassen gleich sein.
  • newInstance verwendet eine deklarierte Variable, die Sie an den Konstruktor für neue Objekte übergeben möchten

Wenn Sie die Fehler nicht werfen möchten, können Sie sie wie folgt abfangen:

public <T extends MobilePage> T tapSignInButton(Class<T> type) {
    // signInButton.click();
    T returnValue = null;
    try {
       returnValue = type.getConstructor(AppiumDriver.class).newInstance(appiumDriver);
    } catch (Exception e) {
        e.printStackTrace();
    }
    return returnValue;
}
MTen
quelle
Nach meinem Verständnis ist dies die beste und eleganteste Art, die Generika zu verwenden.
Cbaldan
2

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?

sk.
quelle
1
Was ist, wenn die Tiere mehrere Methoden haben, die nicht einmal unter eine gemeinsame Aktion fallen, die abstrahiert werden kann? Ich brauche dies für die Kommunikation zwischen Unterklassen mit verschiedenen Aktionen, bei denen ich mit dem Übergeben des Typs einverstanden bin, nicht mit einer Instanz.
Sathish
2
Sie haben wirklich gerade Ihre eigene Frage beantwortet - wenn ein Tier eine einzigartige Aktion hat, müssen Sie auf dieses bestimmte Tier wirken. Wenn ein Tier eine Aktion hat, die mit anderen Tieren gruppiert werden kann, können Sie eine abstrakte oder virtuelle Methode in einer Basisklasse definieren und diese verwenden.
Matt Jordan
2

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.

using System;
using System.Collections.Generic;
using System.Reflection;

namespace GenericsTest
{
class MainClass
{
    public static void Main (string[] args)
    {
        _HasFriends jerry = new Mouse();
        jerry.AddFriend("spike", new Dog());
        jerry.AddFriend("quacker", new Duck());

        jerry.CallFriend<_Animal>("spike").Speak();
        jerry.CallFriend<_Animal>("quacker").Speak();
    }
}

interface _HasFriends
{
    void AddFriend(string name, _Animal animal);

    T CallFriend<T>(string name) where T : _Animal;
}

interface _Animal
{
    void Speak();
}

abstract class AnimalBase : _Animal, _HasFriends
{
    private Dictionary<string, _Animal> friends = new Dictionary<string, _Animal>();


    public abstract void Speak();

    public void AddFriend(string name, _Animal animal)
    {
        friends.Add(name, animal);
    }   

    public T CallFriend<T>(string name) where T : _Animal
    {
        return (T) friends[name];
    }
}

class Mouse : AnimalBase
{
    public override void Speak() { Squeek(); }

    private void Squeek()
    {
        Console.WriteLine ("Squeek! Squeek!");
    }
}

class Dog : AnimalBase
{
    public override void Speak() { Bark(); }

    private void Bark()
    {
        Console.WriteLine ("Woof!");
    }
}

class Duck : AnimalBase
{
    public override void Speak() { Quack(); }

    private void Quack()
    {
        Console.WriteLine ("Quack! Quack!");
    }
}
}
Nestor Ledon
quelle
Diese Frage hat mit Codierung und nicht mit Konzept zu tun.
2

In meinem lib kontraktor habe ich folgendes gemacht:

public class Actor<SELF extends Actor> {
    public SELF self() { return (SELF)_self; }
}

Unterklasse:

public class MyHttpAppSession extends Actor<MyHttpAppSession> {
   ...
}

Zumindest funktioniert dies innerhalb der aktuellen Klasse und wenn eine stark typisierte Referenz vorhanden ist. Mehrfachvererbung funktioniert, wird dann aber sehr knifflig :)

R. Moeller
quelle
1

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:

import java.lang.reflect.InvocationTargetException;
import java.util.HashMap;
import java.util.Map;

abstract class AnimalExample {
    private Map<String,Class<?>> friends = new HashMap<String,Class<?>>();
    private Map<String,Object> theFriends = new HashMap<String,Object>();

    public void addFriend(String name, Object friend){
        friends.put(name,friend.getClass());
        theFriends.put(name, friend);
    }

    public void makeMyFriendSpeak(String name){
        try {
            friends.get(name).getMethod("speak").invoke(theFriends.get(name));
        } catch (IllegalArgumentException e) {
            e.printStackTrace();
        } catch (SecurityException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        } catch (InvocationTargetException e) {
            e.printStackTrace();
        } catch (NoSuchMethodException e) {
            e.printStackTrace();
        }
    } 

    public abstract void speak ();
};

class Dog extends Animal {
    public void speak () {
        System.out.println("woof!");
    }
}

class Duck extends Animal {
    public void speak () {
        System.out.println("quack!");
    }
}

class Cat extends Animal {
    public void speak () {
        System.out.println("miauu!");
    }
}

public class AnimalExample {

    public static void main (String [] args) {

        Cat felix = new Cat ();
        felix.addFriend("Spike", new Dog());
        felix.addFriend("Donald", new Duck());
        felix.makeMyFriendSpeak("Spike");
        felix.makeMyFriendSpeak("Donald");

    }

}

quelle
1

wie wäre es mit

public class Animal {
private Map<String,<T extends Animal>> friends = new HashMap<String,<T extends Animal>>();

public <T extends Animal> void addFriend(String name, T animal){
    friends.put(name,animal);
}

public <T extends Animal> T callFriend(String name){
    return friends.get(name);
}

}}

gafadr
quelle
0

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.

FeralWhippet
quelle
Ich bin mir nicht sicher, was Sie unter "Rückgabetyp eingrenzen" verstehen. Afaik, Java und die meisten typisierten Sprachen überladen Methoden oder Funktionen, die auf dem Rückgabetyp basieren, nicht. Zum Beispiel public int getValue(String name){}ist aus public 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 ...
The One True Colter
In Java können Sie eine Methode in einer Unterklasse überschreiben und einen "engeren" (dh spezifischeren) Rückgabetyp angeben. Siehe stackoverflow.com/questions/14694852/… .
FeralWhippet
-3
public <X,Y> X nextRow(Y cursor) {
    return (X) getRow(cursor);
}

private <T> Person getRow(T cursor) {
    Cursor c = (Cursor) cursor;
    Person s = null;
    if (!c.moveToNext()) {
        c.close();
    } else {
        String id = c.getString(c.getColumnIndex("id"));
        String name = c.getString(c.getColumnIndex("name"));
        s = new Person();
        s.setId(id);
        s.setName(name);
    }
    return s;
}

Sie können jeden Typ zurückgeben und direkt wie erhalten. Sie müssen nicht typisieren.

Person p = nextRow(cursor); // cursor is real database cursor.

Dies ist am besten geeignet, wenn Sie andere Datensätze anstelle von echten Cursorn anpassen möchten.

user6083049
quelle
2
Sie sagen, dass "keine Typografie erforderlich ist", aber es handelt sich eindeutig um Typisierung: (Cursor) cursorzum Beispiel.
Sami Laine
Dies ist eine völlig unangemessene Verwendung von Generika. Dieser Code muss a cursorsein Cursorund gibt immer a Person(oder null) zurück. Durch die Verwendung von Generika wird die Überprüfung dieser Einschränkungen aufgehoben, wodurch der Code unsicher wird.
Andy Turner