with - java pass generic type to method




Wie mache ich die Methode Rückgabetyp generisch? (13)

"Gibt es eine Möglichkeit, den Rückgabetyp zur Laufzeit ohne den zusätzlichen Parameter mit instanceof zu ermitteln?"

Als eine alternative Lösung könnten Sie das Besuchermuster wie folgt verwenden . Machen Sie Animal abstract und implementieren Sie 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);
}

Eine Hundeimplementierung würde dann beispielsweise so 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, wenn er weiß, welchen Typ er hat, die relevante überladene Besuchsmethode des Besuchers auslösen kann, indem er "this" als Parameter übergibt. Andere Unterklassen würden accept () genauso implementieren.

Die Klasse, die subklassenspezifische Methoden aufrufen möchte, muss dann die Visitor-Schnittstelle 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 haben, aber es ist eine Standardmethode, um jeden spezifischen Subtyp mit genau null Instanz von Checks und Null-Typ-Casts in den Griff zu bekommen. Und es ist alles in einer Standardsprache agnostisch gemacht, also ist es nicht nur für Java, aber jede OO-Sprache sollte gleich funktionieren.

Betrachten Sie dieses Beispiel (typisch in OOP-Büchern):

Ich habe eine Animal , in der jedes Animal viele Freunde haben kann.
Und Unterklassen wie Dog , Duck , Mouse usw., die ein bestimmtes Verhalten wie bark() , quack() etc. hinzufügen

Hier ist die Animal :

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

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, so dass ich sagen kann

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

Hier ist ein erster Code mit Rückgabetyp, der der Methode als Parameter ü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 ohne den zusätzlichen Parameter mit instanceof herauszufinden? Oder zumindest indem eine Klasse des Typs statt einer Dummy-Instanz übergeben wird.
Ich verstehe Generics sind für die Kompilierzeit Typprüfung, aber gibt es einen Workaround dafür?


Außerdem können Sie die Methode bitten, den Wert eines bestimmten Typs auf diese Weise zurückzugeben

<T> T methodName(Class<T> var);

Weitere Beispiele finden Sie here bei Oracle Java Dokumentation


Diese Frage ist sehr ähnlich zu Punkt 29 in Effective Java - "Berücksichtigen Sie typsichere heterogene Container." Laz 'Antwort ist Blochs Lösung am nächsten. Sowohl put als auch get sollten jedoch das Klassenliteral für die Sicherheit verwenden. Die Signaturen würden:

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

Innerhalb beider Methoden sollten Sie überprüfen, ob die Parameter vernünftig sind. Weitere Informationen finden Sie unter Effektives Java und die Class Javadoc.


Es gibt noch 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 mehreren Deklarationen von callFriend, aber Sie könnten die allgemeinen Teile auf eine Methode isolieren, die intern aufgerufen wird. Dies erscheint mir viel einfacher als die oben erwähnten Lösungen und benötigt kein zusätzliches Argument, um den Rückgabetyp zu bestimmen.


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

        }
    }

Ich habe einen Artikel geschrieben, der einen Proof of Concept, Support-Klassen und eine Testklasse enthält, die zeigt, wie Super Type Tokens zur Laufzeit von Ihren Klassen abgerufen werden können. Kurz gesagt, erlaubt es Ihnen, an alternative Implementierungen zu delegieren, abhängig von tatsächlichen generischen Parametern, die vom Aufrufer übergeben wurden. Beispiel:

  • TimeSeries<Double> delegiert an eine private innere Klasse, die double[]
  • TimeSeries<OHLC> delegiert an eine private innere Klasse, die ArrayList<OHLC>

Siehe: Verwenden von TypeTokens zum Abrufen generischer Parameter

Vielen Dank

Richard Gomes - Blog


Ich weiß, dass das eine ganz andere Sache ist, die der eine gefragt hat. Eine andere Möglichkeit, dies zu lösen, wäre die Reflexion. Ich meine, das nutzt Generics nicht, aber es lässt Sie in gewisser Weise das Verhalten nachahmen, das Sie ausführen möchten (z. B. einen Hund bellen, einen Entenquaken machen usw.), ohne sich um das Typcasting 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");

    }

}

Nein. Der Compiler kann nicht wissen, welchen Typ jerry.callFriend("spike") würde. Außerdem verbirgt Ihre Implementierung den Cast 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 würde das Erstellen einer abstrakten talk() -Methode und entsprechendes Überschreiben in den Unterklassen 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();

Nicht wirklich, denn wie du sagst, weiß der Compiler nur, dass callFriend () ein Tier zurückgibt, keinen Hund oder eine Ente.

Können Sie nicht eine abstrakte makeNoise () -Methode zu Animal hinzufügen, die von ihren Unterklassen als Bark oder Quacksalber implementiert wird?


Sie könnten callFriend definieren:

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 Compilerwarnungen generiert werden. Natürlich ist dies wirklich nur eine aktualisierte Version von Casting aus den Pre-Generic-Tagen und fügt keine zusätzliche Sicherheit hinzu.


Was Sie hier suchen, ist Abstraktion. Code gegen Interfaces mehr und du solltest weniger casting machen.

Das Beispiel unten ist in C #, aber das Konzept bleibt gleich.

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!");
    }
}
}

Wie du gesagt hast, wäre es okay, wenn du einen Kurs bestanden hast. Du könntest das 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 das, was man mit Java-Generika bekommt. Es gibt eine Möglichkeit, typsichere heterogene Container (THC) mit Super-Typ-Token zu implementieren, aber das hat wieder seine eigenen Probleme.


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 erhalten. Sie müssen nicht tippen.

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

Dies ist am besten, wenn Sie andere Arten von Datensätzen anstelle von echten Cursors anpassen möchten.





return-value