93 votes

Qu'est-ce qui est cool avec les génériques, pourquoi les utiliser ?

J'ai pensé que je pourrais offrir cette balle à quiconque voudrait la frapper hors du parc. Que sont les génériques, quels sont les avantages des génériques, pourquoi, où et comment les utiliser ? Merci de rester assez basique. Merci.

1 votes

Duplicata de stackoverflow.com/questions/77632/ . Mais la réponse est simple, même si les collections ne font pas partie de votre API, je n'aime pas les castings inutiles, même dans l'implémentation interne.

0 votes

La question est assez similaire, mais je ne pense pas que la réponse acceptée réponde à celle-ci.

1 votes

Vérifiez également stackoverflow.com/questions/520527

133voto

ljs Points 16511
  • Vous permet d'écrire du code et d'utiliser des méthodes de bibliothèque qui sont sûres au niveau du type, c'est-à-dire qu'une liste<string> est garantie comme étant une liste de chaînes de caractères.
  • Grâce à l'utilisation des génériques, le compilateur peut effectuer des vérifications au moment de la compilation du code pour la sécurité des types, par exemple : essayez-vous de mettre un int dans cette liste de chaînes de caractères ? L'utilisation d'une ArrayList entraînerait une erreur d'exécution moins transparente.
  • Plus rapide que l'utilisation d'objets, car elle évite le boxing/unboxing (où .net doit convertir les objets en objets). des types de valeur aux types de référence ou vice-versa ) ou le moulage d'objets vers le type de référence requis.
  • Permet d'écrire du code applicable à de nombreux types ayant le même comportement sous-jacent, c'est-à-dire qu'un Dictionary<string, int> utilise le même code sous-jacent qu'un Dictionary<DateTime, double> ; en utilisant les génériques, l'équipe du framework n'a dû écrire qu'un seul morceau de code pour obtenir les deux résultats avec les avantages susmentionnés également.

9 votes

Ah, très bien, et j'aime bien les listes à puces. Je pense que c'est la réponse la plus complète et la plus succincte jusqu'à présent.

0 votes

L'ensemble de l'explication se situe dans le contexte des "collections". je cherche un regard plus large. des idées ?

0 votes

Bonne réponse je veux juste ajouter 2 autres avantages, la restriction générique a 2 avantages en plus 1- Vous pouvez utiliser les propriétés du type contraint dans le type générique (par exemple où T : IComparable permet d'utiliser CompareTo) 2- La personne qui écrira le code après vous saura ce qu'il faut faire.

55voto

James Schek Points 11070

Je déteste vraiment me répéter. Je déteste taper la même chose plus souvent que nécessaire. Je n'aime pas répéter les choses plusieurs fois avec de légères différences.

Au lieu de créer :

class MyObjectList  {
   MyObject get(int index) {...}
}
class MyOtherObjectList  {
   MyOtherObject get(int index) {...}
}
class AnotherObjectList  {
   AnotherObject get(int index) {...}
}

Je peux construire une classe réutilisable... (dans le cas où vous ne voulez pas utiliser la collection brute pour une raison quelconque)

class MyList<T> {
   T get(int index) { ... }
}

Je suis maintenant 3x plus efficace et je ne dois maintenir qu'une seule copie. Pourquoi ne voudriez-vous pas maintenir moins de code ?

Cela est également vrai pour les classes qui ne sont pas des collections, telles qu'une Callable<T> ou un Reference<T> qui doit interagir avec d'autres classes. Voulez-vous vraiment étendre Callable<T> et Future<T> et toutes les autres classes associées pour créer des versions sûres ?

Je ne le fais pas.

1 votes

Je ne suis pas sûr de comprendre. Je ne suggère pas que vous fassiez des wrappers "MyObject", ce serait horrible. Je suggère que si votre collection d'objets est une collection de voitures, alors vous écrivez un objet Garage. Il n'aurait pas get(car), il aurait des méthodes comme buyFuel(100) qui achèterait 100$ de carburant et le distribuerait aux voitures qui en ont le plus besoin. Je parle de vraies classes d'affaires, pas seulement de "wrappers". Par exemple, vous ne devriez presque jamais obtenir (voiture) ou boucler sur eux en dehors de la collection - vous ne demandez pas à un objet pour ses membres, au lieu de cela vous lui demandez de faire une opération pour vous.

0 votes

Même à l'intérieur de votre garage, vous devez avoir une sécurité de type. Donc, soit vous avez List<Cars>, ListOfCars, soit vous faites un casting partout. Je ne ressens pas le besoin d'indiquer le type plus que le minimum nécessaire.

0 votes

Je suis d'accord que la sécurité de type serait bien, mais elle est beaucoup moins utile puisqu'elle est confinée à la classe des garages. Elle est encapsulée et facilement contrôlable, donc mon point de vue est que cela vous donne ce petit avantage au prix d'une nouvelle syntaxe. C'est comme modifier la constitution américaine pour rendre illégal le fait de brûler un feu rouge - oui, cela devrait toujours être illégal, mais cela justifie-t-il un amendement ? Je pense également que cela encourage les gens à NE PAS utiliser l'encapsulation dans ce cas, ce qui est en fait comparativement dommageable.

22voto

coobird Points 70356

Ne pas avoir besoin de typage est l'un des plus grands avantages des génériques Java. car il effectue une vérification des types au moment de la compilation. Cela réduira la possibilité de ClassCastException qui peuvent être lancés au moment de l'exécution, et peuvent conduire à un code plus robuste.

Mais je soupçonne que vous en êtes pleinement conscient.

Chaque fois que je regarde les génériques, ça me donne un mal de tête. Je trouve que la meilleure partie de Java est sa simplicité et sa syntaxe minimale syntaxe minimale et les génériques ne sont pas simples et ajoutent une quantité significative de nouvelle syntaxe.

Au début, je ne voyais pas non plus l'avantage des génériques. J'ai commencé à apprendre Java à partir de la syntaxe 1.4 (même si Java 5 était sorti à l'époque) et lorsque j'ai rencontré les génériques, j'ai eu l'impression que c'était plus de code à écrire, et je n'ai pas vraiment compris les avantages.

Les IDE modernes facilitent l'écriture de code avec des génériques.

La plupart des IDE modernes et décents sont suffisamment intelligents pour aider à écrire du code avec des génériques, en particulier avec la complétion de code.

Voici un exemple de création d'un Map<String, Integer> avec un HashMap . Le code que je devrais taper est :

Map<String, Integer> m = new HashMap<String, Integer>();

Et en effet, cela fait beaucoup de choses à taper juste pour faire une nouvelle HashMap . Cependant, en réalité, je n'ai eu qu'à taper autant de mots pour qu'Eclipse sache ce dont j'avais besoin :

Map<String, Integer> m = new Ha Ctrl + Space

C'est vrai, j'ai dû sélectionner HashMap à partir d'une liste de candidats, mais en fait, l'IDE savait ce qu'il fallait ajouter, y compris les types génériques. Avec les bons outils, utiliser des génériques n'est pas si difficile.

De plus, puisque les types sont connus, lors de la récupération d'éléments de la collection générique, l'IDE agira comme si cet objet était déjà un objet de son type déclaré -- il n'est pas nécessaire de faire un casting pour que l'IDE sache quel est le type de l'objet.

L'un des principaux avantages des génériques réside dans la manière dont ils s'adaptent aux nouvelles fonctionnalités de Java 5. Voici un exemple d'introduction de nombres entiers dans un fichier Set et de calculer son total :

Set<Integer> set = new HashSet<Integer>();
set.add(10);
set.add(42);

int total = 0;
for (int i : set) {
  total += i;
}

Dans ce morceau de code, trois nouvelles fonctionnalités de Java 5 sont présentes :

Premièrement, les génériques et l'autoboxage des primitives permettent les lignes suivantes :

set.add(10);
set.add(42);

Le nombre entier 10 est autoboxé dans un Integer avec la valeur de 10 . (Et pareil pour 42 ). Alors que Integer est jeté dans le Set qui est connu pour tenir Integer s. Essayer d'ajouter un String provoquerait une erreur de compilation.

Ensuite, la boucle for-each prend les trois :

for (int i : set) {
  total += i;
}

Tout d'abord, le Set contenant Integer sont utilisés dans une boucle for-each. Chaque élément est déclaré comme étant un int et cela est autorisé comme le Integer est ramené à la primitive int . Et le fait que cet unboxing se produise est connu parce que generics a été utilisé pour spécifier qu'il y avait des Integer tenues dans les Set .

Les génériques peuvent être la colle qui rassemble les nouvelles fonctionnalités introduites dans Java 5, et cela rend le codage plus simple et plus sûr. Et la plupart du temps, les IDE sont suffisamment intelligents pour vous aider avec de bonnes suggestions, donc en général, il n'y aura pas beaucoup plus de saisie.

Et franchement, comme on peut le voir dans les Set Par exemple, je pense que l'utilisation des fonctionnalités de Java 5 peut rendre le code plus concis et plus robuste.

Edit - Un exemple sans génériques

Voici une illustration de ce qui précède Set exemple sans l'utilisation de génériques. C'est possible, mais ce n'est pas vraiment agréable :

Set set = new HashSet();
set.add(10);
set.add(42);

int total = 0;
for (Object o : set) {
  total += (Integer)o;
}

(Remarque : le code ci-dessus générera un avertissement de conversion non vérifié au moment de la compilation).

Lorsque l'on utilise des collections non génériques, les types qui sont introduits dans la collection sont des objets de type Object . Par conséquent, dans cet exemple, une Object est ce qui est en train d'être add dans l'ensemble.

set.add(10);
set.add(42);

Dans les lignes ci-dessus, l'autoboxing est en jeu -- la primitive int valeur 10 et 42 sont transportés par autobus dans Integer qui sont ajoutés à la Set . Cependant, gardez à l'esprit que le Integer sont traités comme des objets Object car il n'y a pas d'information de type pour aider le compilateur à savoir de quel type sont les Set doit s'attendre.

for (Object o : set) {

C'est la partie qui est cruciale. Si la boucle for-each fonctionne, c'est parce que la boucle Set met en œuvre la Iterable qui renvoie un Iterator avec des informations sur le type, si elles sont présentes. ( Iterator<T> c'est-à-dire )

Cependant, étant donné qu'il n'y a pas d'information sur le type, l'option Set retournera un Iterator qui retournera les valeurs dans le Set comme Object et c'est pourquoi l'élément récupéré dans la boucle for-each doit être de type Object .

Maintenant que le Object est récupéré à partir de la Set il doit être transformé en un Integer manuellement pour effectuer l'ajout :

  total += (Integer)o;

Ici, un typecast est effectué à partir d'un Object à un Integer . Dans ce cas, nous savons que cela fonctionnera toujours, mais le typage manuel me donne toujours l'impression qu'il s'agit d'un code fragile qui pourrait être endommagé si une modification mineure est apportée ailleurs. (Je pense que chaque typecast est un ClassCastException en attente de se produire, mais je m'égare...)

Le site Integer est maintenant décomposé en un int et autorisé à effectuer l'addition dans le int variable total .

J'espère avoir pu illustrer que les nouvelles fonctionnalités de Java 5 sont possibles à utiliser avec du code non générique, mais que ce n'est tout simplement pas aussi propre et direct que d'écrire du code avec des génériques. Et, à mon avis, pour tirer pleinement parti des nouvelles fonctionnalités de Java 5, on devrait se pencher sur les génériques, si au moins, permet des contrôles au moment de la compilation pour empêcher les typescasts invalides de lancer des exceptions au moment de l'exécution.

0 votes

L'intégration avec la boucle for améliorée est vraiment bien (bien que je pense que cette fichue boucle est cassée parce que je ne peux pas utiliser exactement la même syntaxe avec une collection non générique - elle devrait simplement utiliser le cast/autobox to int, elle a toutes les informations dont elle a besoin !), mais vous avez raison, l'aide dans l'IDE est probablement bonne. Je ne sais toujours pas si c'est suffisant pour justifier la syntaxe supplémentaire, mais au moins c'est quelque chose dont je peux bénéficier (ce qui répond à ma question).

0 votes

@Bill K : J'ai ajouté un exemple d'utilisation des fonctionnalités de Java 5 sans utiliser les génériques.

0 votes

C'est un bon point, je n'avais pas utilisé cela (j'ai mentionné que j'étais bloqué, sur 1.4 et plus tôt depuis des années maintenant). Je suis d'accord pour dire qu'il y a des avantages, mais les conclusions auxquelles je parviens sont que A) ils ont tendance à être très avantageux pour les personnes qui n'utilisent pas correctement OO et sont moins utiles pour ceux qui le font, et B) les avantages que vous avez énumérés sont des avantages, mais à peine assez valables pour justifier même un petit changement de syntaxe, sans parler des grands changements nécessaires pour vraiment comprendre les génériques tels qu'ils sont implémentés en Java. Mais au moins, il y a des avantages.

15voto

Si vous deviez effectuer une recherche dans la base de données des bogues de Java juste avant la sortie de la version 1.5, vous trouveriez sept fois plus de bogues avec les éléments suivants NullPointerException que ClassCastException . Il ne semble donc pas que ce soit une fonctionnalité idéale pour trouver des bogues, ou du moins des bogues qui persistent après un petit test de fumée.

Pour moi, l'énorme avantage des génériques est qu'ils documentent en code informations importantes sur le type de produit. Si je ne voulais pas que ces informations de type soient documentées dans le code, j'utiliserais un langage à typage dynamique, ou au moins un langage avec une inférence de type plus implicite.

Garder les collections d'un objet pour lui-même n'est pas un mauvais style (mais alors le style commun est d'ignorer effectivement l'encapsulation). Cela dépend plutôt de ce que vous faites. Passer les collections aux "algorithmes" est légèrement plus facile à vérifier (au moment de la compilation ou avant) avec les génériques.

6 votes

Non seulement il est documenté (ce qui en soi est une grande victoire), mais il est appliqué par le compilateur.

0 votes

Bon point, mais n'est-ce pas également possible en enfermant la collection dans une classe qui définit "une collection de ce type d'objet" ?

1 votes

James : Oui, c'est le point sur le fait qu'il soit documenté. en code .

11voto

Apocalisp Points 22526

Faciliter les génériques en Java polymorphisme paramétrique . Grâce aux paramètres de type, vous pouvez transmettre des arguments aux types. Tout comme une méthode comme String foo(String s) modélise un certain comportement, pas seulement pour une chaîne de caractères particulière, mais pour toute chaîne de caractères. s donc un type comme List<T> modélise un certain comportement, pas seulement pour un type spécifique, mais pour tout type . List<T> dit que pour tout type T il y a un type de List dont les éléments sont T s . Donc List est en fait un constructeur de type . Il prend un type comme argument et construit un autre type comme résultat.

Voici quelques exemples de types génériques que j'utilise tous les jours. Tout d'abord, une interface générique très utile :

public interface F<A, B> {
  public B f(A a);
}

Cette interface indique que pour quelques deux types, A et B il y a une fonction (appelée f ) qui prend un A et renvoie un B . Lorsque vous implémentez cette interface, A et B peuvent être de n'importe quel type, du moment que vous fournissez une fonction f qui prend le premier et retourne le second. Voici un exemple d'implémentation de l'interface :

F<Integer, String> intToString = new F<Integer, String>() {
  public String f(int i) {
    return String.valueOf(i);
  }
}

Avant les génériques, le polymorphisme était réalisé par sous-classement en utilisant le extends mot-clé. Avec les génériques, nous pouvons en fait nous passer de la sous-classification et utiliser le polymorphisme paramétrique à la place. Par exemple, considérons une classe (générique) paramétrée utilisée pour calculer des codes de hachage pour n'importe quel type. Au lieu de surcharger Object.hashCode(), nous utiliserions une classe générique comme celle-ci :

public final class Hash<A> {
  private final F<A, Integer> hashFunction;

  public Hash(final F<A, Integer> f) {
    this.hashFunction = f;
  }

  public int hash(A a) {
    return hashFunction.f(a);
  }
}

C'est beaucoup plus flexible que l'utilisation de l'héritage, car nous pouvons rester dans le thème de l'utilisation de la composition et du polymorphisme paramétrique sans verrouiller des hiérarchies fragiles.

Les génériques de Java ne sont pas parfaits pour autant. Vous pouvez faire abstraction des types, mais pas des constructeurs de types, par exemple. Autrement dit, vous pouvez dire "pour tout type T", mais vous ne pouvez pas dire "pour tout type T qui prend un paramètre de type A".

J'ai écrit un article sur ces limites des génériques Java, ici.

L'un des grands avantages des génériques est qu'ils vous permettent d'éviter les sous-classes. La sous-classification a tendance à donner lieu à des hiérarchies de classes fragiles, difficiles à étendre, et à des classes difficiles à comprendre individuellement sans regarder l'ensemble de la hiérarchie.

Avant les génériques, vous pouviez avoir des classes comme Widget prolongé par FooWidget , BarWidget et BazWidget Avec les génériques, vous pouvez avoir une seule classe générique. Widget<A> qui prend un Foo , Bar ou Baz dans son constructeur pour vous donner Widget<Foo> , Widget<Bar> et Widget<Baz> .

0 votes

Ce serait un bon point concernant le sous-classement si Java n'avait pas d'interfaces. Ne serait-il pas correct que FooWidget, BarWidtet et BazWidget implémentent tous Widget ? Je peux me tromper, mais Mais c'est un très bon point si je me trompe.

0 votes

Pourquoi avez-vous besoin d'une classe générique dynamique pour calculer les valeurs de hachage ? Une fonction statique avec des paramètres appropriés ne serait-elle pas plus efficace ?

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