650 votes

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

Considérons cet exemple (typique en programmation orientée objet, des livres):
J'ai un Animal de la classe, où chaque Animal peut avoir beaucoup d'amis.
Et les sous-classes comme le Chien, le Canard, Souris, etc, qui ajoutent un comportement spécifique, comme bark(), quack() etc.

Voici l'Animal de la classe:

public class Animal {
    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);
    }
}

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

Est il possible que je peux utiliser des génériques pour le type de retour pour se débarrasser de la typecasting, de sorte que je peux dire

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

Voici quelques code initial avec le type de retour transmis à la méthode comme un paramètre qui n'est jamais utilisé.

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

Est-il un moyen de comprendre le type de retour à l'exécution sans le paramètre supplémentaire à l'aide de instanceof? Ou au moins par le passage d'une catégorie du type, au lieu d'une instance fictive.
Je comprends les génériques sont pour moment de la compilation, la vérification de type, mais est-il une solution pour cela?

984voto

laz Points 12212

Vous pouvez définir callFriend cette façon:

 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 de compilateur. Bien sûr, il s'agit juste d'une version mise à jour de la distribution des jours pré-génériques et n'ajoute aucune sécurité supplémentaire.

129voto

David Schmitt Points 29384

Le compilateur ne peut pas savoir quel type retournera jerry.callFriend("spike") . En outre, votre implémentation masque uniquement la conversion 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, la création d'une méthode abstraite talk() et son remplacement approprié dans les sous-classes vous serviraient beaucoup mieux:

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

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

126voto

Michael Myers Points 82361

Vous pourriez la mettre en œuvre comme ceci:

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

(Oui, c'est légal, code; voir à cette question.)

Le type de retour sera déduit à partir de l'appelant. Toutefois, note l' @SuppressWarnings d'annotation: qui vous dit que ce code n'est pas typesafe. Vous devez vérifier vous-même, ou vous pourriez obtenir de l' ClassCastExceptions lors de l'exécution.


EDIT: Malheureusement, la façon dont vous l'utilisez (sans affecter la valeur de retour d'une variable temporaire), le seul moyen de faire le bonheur du compilateur est de l'appeler comme ceci:

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

C'est peut-être un peu plus agréable que le casting, vous êtes probablement mieux de donner l' Animal classe abstraite talk() méthode, comme David Schmitt dit.

31voto

Craig P. Motlin Points 11814

Cette question est très similaire à l'Article 29 de dans Efficace Java - "Envisager de typesafe hétérogène des conteneurs." Laz de réponse est le plus proche de Bloch de la solution. Toutefois, les deux put et get devrait utiliser la Classe littéral pour la sécurité. Les signatures deviendrait:

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

L'intérieur de ces deux méthodes, vous devriez vérifier que les paramètres sont corrects. Voir Efficace de Java et de la Classe javadoc pour plus d'info.

6voto

Michael Borgwardt Points 181658

Pas possible. Comment la carte est-elle censée savoir quelle sous-classe d'Animal elle va obtenir, avec seulement une clé de type String?

La seule façon de procéder serait que chaque animal n'accepte qu'un seul type d'ami (alors il pourrait s'agir d'un paramètre de la classe Animal) ou de la méthode callFriend () un paramètre type. Mais il semble vraiment que vous manquez le point d'héritage: vous ne pouvez traiter les sous-classes que de manière uniforme en utilisant exclusivement les méthodes de la super-classe.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X