Question Comment puis-je rendre le type de retour de la méthode générique?


Considérez cet exemple (typique des livres de la POO):

j'ai un Animal classe, où chaque Animal peut avoir beaucoup d'amis.
Et des sous-classes comme Dog, Duck, Mouse etc qui ajoutent un comportement spécifique comme bark(), quack() etc.

Ici se trouve le Animal classe:

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

Et voici un extrait de code avec beaucoup de 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();

Y a-t-il un moyen d'utiliser des génériques pour le type de retour afin de se débarrasser de la conversion, afin que je puisse dire

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

Voici un code initial avec le type de retour transmis à la méthode en tant que paramètre jamais utilisé.

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

Existe-t-il un moyen de déterminer le type de retour à l'exécution sans le paramètre supplémentaire utilisant instanceof? Ou au moins en passant une classe du type au lieu d'une instance fictive.
Je comprends que les génériques sont pour la vérification de type de compilation, mais y a-t-il une solution à ce problème?


468
2018-01-16 15:43


origine


Réponses:


Vous pouvez définir callFriend par ici:

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

Alors appelez-le comme tel:

jerry.callFriend("spike", Dog.class).bark();
jerry.callFriend("quacker", Duck.class).quack();

Ce code a l'avantage de ne générer aucun avertissement du compilateur. Bien sûr, il ne s’agit que d’une version actualisée du casting des jours pré-génériques et n’ajoute aucune sécurité supplémentaire.


727
2018-01-16 15:57



Non, le compilateur ne peut pas savoir quel type jerry.callFriend("spike") retournerais. En outre, votre implémentation ne fait que masquer la distribution dans la méthode sans aucune sécurité de type supplémentaire. Considère ceci:

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

Dans ce cas précis, créer un résumé talk() méthode et en l'annulant de manière appropriée dans les sous-classes vous servirait beaucoup mieux:

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

jerry.callFriend("spike").talk();
jerry.callFriend("quacker").talk();

103
2018-01-16 15:50



Vous pourriez l'implémenter comme ceci:

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

(Oui, c'est un code légal; voir Java Generics: type générique défini comme type de retour uniquement.)

Le type de retour sera déduit de l'appelant. Cependant, notez la @SuppressWarnings annotation: cela vous dit que ce code n'est pas sécurisé. Vous devez le vérifier vous-même ou vous pouvez obtenir ClassCastExceptions lors de l'exécution.

Malheureusement, la façon dont vous l'utilisez (sans affecter la valeur de retour à une variable temporaire), la seule façon de rendre le compilateur heureux, c'est de l'appeler comme ceci:

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

Bien que cela puisse être un peu plus agréable que le casting, vous êtes probablement mieux de donner le Animal classer un résumé talk() méthode, comme l’a dit David Schmitt.


90
2018-01-16 15:51



Cette question est très similaire à Article 29 en Java efficace - "Considérons des conteneurs hétérogènes de types". La réponse de Laz est la plus proche de la solution de Bloch. Cependant, put et get doivent utiliser le littéral de classe pour la sécurité. Les signatures deviendraient:

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

Dans les deux méthodes, vous devez vérifier que les paramètres sont sains. Voir Java efficace et le Classe javadoc pour plus d'informations.


26
2018-01-16 17:06



De plus, vous pouvez demander à la méthode de renvoyer la valeur dans un type donné de cette façon

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

Plus d'exemples ici à la documentation Oracle Java


11
2018-06-24 05:12



Comme vous l'avez dit, passer une classe serait bien, vous pourriez écrire ceci:

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

Et puis utilisez-le comme ceci:

jerry.callFriend("spike", Dog.class).bark();
jerry.callFriend("quacker", Duck.class).quack();

Pas parfait, mais c'est à peu près la même chose avec Java Generics. Il y a un moyen de mettre en œuvre Conteneurs Heterogenes Typesafe (THC) utilisant des jetons Super Type, mais cela a encore ses propres problèmes.


9
2018-01-16 15:57



Basé sur la même idée que Super Type Tokens, vous pouvez créer un identifiant typé à utiliser à la place d'une chaîne:

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];
  }
}

Mais je pense que cela peut aller à l'encontre du but, car vous devez maintenant créer de nouveaux objets id pour chaque chaîne et les conserver (ou les reconstruire avec les informations de type correctes).

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

Mais vous pouvez maintenant utiliser la classe comme vous le vouliez à l'origine, sans les conversions.

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

Cela ne fait que cacher le paramètre type dans l'identifiant, bien que cela signifie que vous pouvez récupérer le type de l'identifiant plus tard si vous le souhaitez.

Vous devez également implémenter les méthodes de comparaison et de hachage de TypedID si vous souhaitez pouvoir comparer deux instances identiques d'un identifiant.


7
2018-01-23 02:00