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

87

Je pensais offrir ce softball à quiconque voudrait le frapper hors du parc. Que sont les génériques, quels sont les avantages des génériques, pourquoi, où, comment les utiliser? Veuillez garder cela assez basique. Merci.

MrBoJangles
la source
1
Duplicata de stackoverflow.com/questions/77632/… . Mais réponse simple, même si les collections ne font pas partie de votre API, je n'aime pas le casting inutile, même dans l'implémentation interne.
Matthew Flaschen
La question est assez similaire, mais je ne pense pas que la réponse acceptée réponde à celle-ci.
Tom Hawtin - tackline
1
Consultez également stackoverflow.com/questions/520527
Clint Miller
4
@MatthewFlaschen bizarre, votre duplicata dirige vers cette question ... pépin dans la matrice!
jcollum
@jcollum, je pense que la question initiale sur laquelle j'ai posté ce commentaire a été fusionnée avec cette question.
Matthew Flaschen

Réponses:

126
  • Vous permet d'écrire du code / d'utiliser des méthodes de bibliothèque qui sont de type sécurisé, c'est-à-dire qu'une List <string> est garantie d'être une liste de chaînes.
  • En raison de l'utilisation de génériques, le compilateur peut effectuer des vérifications à la compilation sur le code pour la sécurité de type, c'est-à-dire essayez-vous de mettre un int dans cette liste de chaînes? L'utilisation d'un ArrayList entraînerait une erreur d'exécution moins transparente.
  • Plus rapide que d'utiliser des objets car cela évite soit le boxing / unboxing (où .net doit convertir les types valeur en types référence ou vice-versa ) soit le transtypage d'objets vers le type référence requis.
  • Permet d'écrire du code qui s'applique à de nombreux types avec 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 des génériques, l'équipe du framework n'a eu qu'à écrire un morceau de code pour obtenir les deux résultats avec les avantages susmentionnés.
ljs
la source
9
Ah, très bien, et j'aime les listes à puces. Je pense que c'est la réponse la plus complète et la plus succincte à ce jour.
MrBoJangles le
toute l'explication est dans le contexte des «collections». je recherche un look plus large. Des pensées?
jungle_mole
bonne réponse je veux juste ajouter 2 autres avantages, la restriction générique a 2 avantages à côté 1- Vous pouvez utiliser les propriétés du type contraint dans le type générique (par exemple où T: IComparable propose d'utiliser CompareTo) 2- La personne qui écrira le code après vous saurez ce qui va faire
Hamit YILDIRIM
52

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 créer une classe réutilisable ... (dans le cas où vous ne souhaitez pas utiliser la collection brute pour une raison quelconque)

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

Je suis maintenant 3 fois plus efficace et je n'ai plus qu'à en conserver une copie. Pourquoi ne voudriez-vous pas conserver moins de code?

Cela est également vrai pour les classes non-collection telles que a Callable<T>ou a Reference<T>qui doivent 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 de type sécurisé?

Je ne.

James Schek
la source
1
Je ne suis pas sûr de comprendre. Je ne vous suggère pas de créer des wrappers "MyObject", ce serait horrible. Je suggère que si votre collection d'objets est une collection de Cars, alors vous écrivez un objet Garage. Il n'aurait pas obtenu (voiture), il aurait des méthodes comme buyFuel (100) qui achèteraient 100 $ de carburant et le distribueraient aux voitures qui en ont le plus besoin. Je parle de vraies classes de commerce, pas seulement de "wrappers". Par exemple, vous ne devriez presque jamais obtenir (car) ou faire une boucle sur eux en dehors de la collection - vous ne demandez pas à un objet pour ses membres, vous lui demandez plutôt de faire une opération pour vous.
Bill K
Même dans votre garage, vous devriez avoir une sécurité de type. Donc, soit vous avez List <Cars>, ListOfCars, soit vous lancez partout. Je ne ressens pas le besoin d'indiquer le type plus que le temps minimum nécessaire.
James Schek
Je suis d'accord que la sécurité de type serait bien, mais c'est beaucoup moins utile car elle est limitée à la classe garage. C'est encapsulé et facilement contrôlé alors, donc mon point 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égale le 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 pour ce cas, ce qui est en fait relativement nocif.
Bill K
Bill - vous avez peut-être un point sur le fait de ne pas utiliser l'encapsulation, mais le reste de votre argument est une mauvaise comparaison. Le coût d'apprentissage de la nouvelle syntaxe est minime dans ce cas (comparez cela aux lambdas en C #); comparer cela à une modification de la Constitution n'a aucun sens. La Constitution n'a pas l'intention de spécifier le code de la route. C'est plus comme passer à une version imprimée Serif-Font sur un tableau d'affichage au lieu d'une copie manuscrite sur parchemin. C'est le même sens, mais c'est beaucoup plus clair et plus facile à lire.
James Schek
L'exemple rend le concept plus pratique. Merci pour ça.
RayLoveless
22

Ne pas avoir besoin de typer est l'un des plus grands avantages des génériques Java , car il effectuera une vérification de type au moment de la compilation. Cela réduira la possibilité que des ClassCastExceptions puissent être lancés au moment de l'exécution, et peut conduire à un code plus robuste.

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

Chaque fois que je regarde Generics, cela me donne mal à la tête. Je trouve que la meilleure partie de Java est sa simplicité et sa 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 les avantages 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 quand j'ai rencontré des génériques, j'ai senti que c'était plus de code à écrire, et je n'en comprenais vraiment pas 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 a HashMap. Le code que je devrais saisir est:

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

Et en effet, c'est beaucoup à taper juste pour en faire un nouveau HashMap. Cependant, en réalité, je n'avais qu'à taper cela bien avant qu'Eclipse ne sache ce dont j'avais besoin:

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

C'est vrai, j'ai eu besoin de sélectionner HashMap dans une liste de candidats, mais fondamentalement, l'EDI savait quoi ajouter, y compris les types génériques. Avec les bons outils, utiliser des génériques n'est pas si mal.

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

Un avantage clé des génériques vient de la façon dont ils fonctionnent bien avec les nouvelles fonctionnalités de Java 5. Voici un exemple de lancer des entiers dans a Setet 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 Java 5 sont présentes:

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

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

L'entier 10est automatiquement converti en un Integeravec la valeur de 10. (Et pareil pour 42). Ensuite, cela Integerest jeté dans le Setqui est connu pour détenir l' Integerart. Essayer de lancer un Stringprovoquerait une erreur de compilation.

Ensuite, la boucle for for-each prend les trois:

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

Tout d'abord, les Setconteneurs Integers sont utilisés dans une boucle for-each. Chaque élément est déclaré comme étant un intet cela est autorisé car le Integerest renvoyé à la primitive int. Et le fait que ce déballage se produise est connu car des génériques ont été utilisés pour spécifier qu'il y avait des Integercontenus dans le fichier Set.

Les génériques peuvent être le ciment qui rassemble les nouvelles fonctionnalités introduites dans Java 5, et cela rend simplement le codage plus simple et plus sûr. Et la plupart du temps, les IDE sont assez intelligents pour vous aider avec de bonnes suggestions, donc généralement, il ne sera pas beaucoup plus de frappe.

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

Edit - Un exemple sans génériques

Ce qui suit est une illustration de l' Setexemple ci-dessus 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 cochée au moment de la compilation.)

Lors de l'utilisation de collections non génériques, les types qui sont entrés dans la collection sont des objets de type Object. Par conséquent, dans cet exemple, a Objectest ce qui est addédité dans l'ensemble.

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

Dans les lignes ci-dessus, l'autoboxing est en jeu - la intvaleur primitive 10et 42sont autoboxed en Integerobjets, qui sont ajoutés au Set. Cependant, gardez à l'esprit que les Integerobjets sont traités en tant que Objects, car il n'y a aucune information de type pour aider le compilateur à savoir à quel type il Setdoit s'attendre.

for (Object o : set) {

C'est la partie qui est cruciale. La raison pour laquelle la boucle for-each fonctionne est que le Setimplémente l' Iterableinterface, qui renvoie une Iteratorinformation de type avec, si elle est présente. (Iterator<T> , c'est.)

Cependant, comme il n'y a pas d'informations de type, le Setrenverra un Iteratorqui retournera les valeurs dans Setas Objects, et c'est pourquoi l'élément en cours de récupération dans la boucle for-each doit être de typeObject .

Maintenant que le Objectest récupéré à partir de Set, il doit être converti Integermanuellement en un pour effectuer l'ajout:

  total += (Integer)o;

Ici, un transtypage est effectué d'un Objectà un Integer. Dans ce cas, nous savons que cela fonctionnera toujours, mais le typage manuel me fait toujours sentir que c'est un code fragile qui pourrait être endommagé si une modification mineure est apportée ailleurs. (Je pense que chaque typage ClassCastExceptionattend, mais je m'éloigne du sujet ...)

Le Integerest maintenant déballé dans un intet autorisé à effectuer l'ajout dans la intvariable total.

J'espère pouvoir illustrer que les nouvelles fonctionnalités de Java 5 peuvent être utilisées avec du code non générique, mais ce n'est tout simplement pas aussi clair et simple que l'écriture de code avec des génériques. Et, à mon avis, pour tirer pleinement parti des nouvelles fonctionnalités de Java 5, il faut se pencher sur les génériques, si à tout le moins, permet des vérifications à la compilation pour empêcher les typecasts invalides de lever des exceptions au moment de l'exécution.

coobird
la source
L'intégration avec la boucle for améliorée est vraiment sympa (même si je pense que la putain de boucle est cassée parce que je ne peux pas utiliser exactement la même syntaxe avec une collection non générique - il devrait simplement utiliser le cast / autobox à int, c'est obtenu toutes les informations dont il a besoin!), mais vous avez raison, l'aide de l'EDI est probablement bonne. Je ne sais toujours pas si cela suffit pour justifier la syntaxe supplémentaire, mais au moins c'est quelque chose dont je peux bénéficier (qui répond à ma question).
Bill K
@Bill K: J'ai ajouté un exemple d'utilisation des fonctionnalités de Java 5 sans utiliser de génériques.
coobird
C'est un bon point, je n'avais pas utilisé ça (j'ai mentionné que j'étais bloqué, sur 1.4 et 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 assez avantageux pour les personnes qui n'utilisent pas correctement OO et sont moins utiles à ceux qui le font, et B) Les avantages que vous ' Voici quelques-uns des avantages, mais ils ne valent guère la peine de justifier même un petit changement de syntaxe, sans parler des changements importants requis pour vraiment grok les génériques implémentés en Java. Mais au moins il y a des avantages.
Bill K
15

Si vous cherchiez dans la base de données de bogues Java juste avant la sortie de la version 1.5, vous trouverez sept fois plus de bogues avec NullPointerExceptionque ClassCastException. Il ne semble donc pas que ce soit une excellente fonctionnalité 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 dans le code des informations de type importantes. Si je ne voulais pas que ces informations de type soient documentées dans le code, alors j'utiliserais un langage typé dynamiquement, 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 efficacement l'encapsulation). Cela dépend plutôt de ce que vous faites. Passer des collections à des «algorithmes» est légèrement plus facile à vérifier (au moment de la compilation ou avant) avec des génériques.

Tom Hawtin - Tacle
la source
6
Non seulement il est documenté (ce qui en soi est une énorme victoire), mais il est appliqué par le compilateur.
James Schek
Bon point, mais n'est-ce pas aussi accompli en enfermant la collection dans une classe qui définit «une collection de ce type d'objet»?
Bill K
1
James: Oui, c'est le fait que cela soit documenté dans le code .
Tom Hawtin - tackline
Je ne pense pas connaître de bonnes bibliothèques qui utilisent des collections pour les "Algorithmes" (ce qui implique peut-être que vous parlez de "Fonctions", ce qui me porte à croire qu'elles devraient être une méthode dans l'objet de la collection). Je pense que le JDK extrait à peu près toujours un joli tableau propre qui est une copie des données afin qu'il ne puisse pas être dérangé - au moins le plus souvent.
Bill K
De plus, le fait d'avoir un objet décrivant exactement comment la collection est utilisée et limitant son utilisation n'est-il pas une forme BEAUCOUP meilleure de documentation dans le code? Je trouve les informations données dans un générique extrêmement redondantes dans un bon code.
Bill K
11

Les génériques en Java facilitent le polymorphisme paramétrique . Au moyen de paramètres de type, vous pouvez passer des arguments aux types. Tout comme une méthode comme String foo(String s)modélise un comportement, pas seulement pour une chaîne particulière, mais pour n'importe quelle chaîne s, un type comme List<T>modélise un comportement, pas seulement pour un type spécifique, mais pour n'importe quel type . List<T>dit que pour tout type T, il existe un type Listdont les éléments sont Ts . Il en Listva de même pour un constructeur de type . Il prend un type comme argument et en construit un autre.

Voici quelques exemples de types génériques que j'utilise quotidiennement. 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 deux types, Aet B, il existe une fonction (appelée f) qui prend un Aet retourne un B. Lorsque vous implémentez cette interface, Ail Bpeut s'agir de tous les types que vous souhaitez, à condition de fournir une fonction fqui prend la première et renvoie la seconde. 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 obtenu par sous-classement à l'aide du extendsmot - clé. Avec les génériques, nous pouvons en fait supprimer les sous-classes et utiliser le polymorphisme paramétrique à la place. Par exemple, considérons une classe paramétrée (générique) utilisée pour calculer les codes de hachage pour tout type. Au lieu de remplacer 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 avec le thème de l'utilisation de la composition et du polymorphisme paramétrique sans verrouiller les hiérarchies fragiles.

Les génériques de Java ne sont cependant pas parfaits. Vous pouvez abstraire des types, mais vous ne pouvez pas abstraire 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.

Un énorme avantage avec les génériques est qu'ils vous permettent d'éviter les sous-classes. Le sous-classement a tendance à entraîner des hiérarchies de classes fragiles qui sont difficiles à étendre et des classes difficiles à comprendre individuellement sans examiner la hiérarchie entière.

Wereas avant génériques que vous pourriez avoir des classes comme Widgetprolongée par FooWidget, BarWidgetet BazWidget, avec les génériques , vous pouvez avoir une seule classe générique Widget<A>qui prend Foo, Barou Bazdans son constructeur pour vous donner Widget<Foo>, Widget<Bar>et Widget<Baz>.

Apocalisp
la source
Ce serait un bon point à propos du sous-classement si Java n'avait pas d'interfaces. Ne serait-il pas correct que FooWidget, BarWidtet et BazWidget implémentent tous Widget? Je pourrais me tromper à ce sujet. Mais c'est un très bon point si je me trompe.
Bill K
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?
Ant_222
8

Les génériques évitent le coup de performance de la boxe et du déballage. En gros, regardez ArrayList vs List <T>. Les deux font les mêmes choses de base, mais List <T> sera beaucoup plus rapide car vous n'avez pas à boxer vers / depuis l'objet.

Darren Kopp
la source
C'est l'essence même, n'est-ce pas? Agréable.
MrBoJangles
Eh bien pas entièrement; ils sont utiles pour la sécurité des types + en évitant les moulages pour les types de références sans que le boxing / unboxing n'entre en jeu.
ljs
Donc, je suppose que la question qui suit est: "Pourquoi voudrais-je utiliser une ArrayList?" Et, au hasard d'une réponse, je dirais que les ArrayLists conviennent tant que vous ne vous attendez pas à emballer et déballer leurs valeurs. Commentaires?
MrBoJangles
Non, ils ont disparu depuis .net 2; seulement utile pour la compatibilité ascendante. Les ArrayLists sont plus lents, ne peuvent pas être saisis et nécessitent un boxing / unboxing ou un casting.
ljs
Si vous ne stockez pas de types de valeurs (par exemple ints ou structs), il n'y a pas de boxing lors de l'utilisation d'ArrayList - les classes sont déjà «object». Utiliser List <T> est toujours bien meilleur en raison du type de sécurité, et si vous voulez vraiment stocker des objets, alors utiliser List <object> est mieux.
Wilka
6

Le meilleur avantage des génériques est la réutilisation du code. Disons que vous avez beaucoup d'objets métier et que vous allez écrire un code TRÈS similaire pour chaque entité pour effectuer les mêmes actions. (IE Linq aux opérations SQL).

Avec les génériques, vous pouvez créer une classe qui sera capable de fonctionner avec l'un des types qui héritent d'une classe de base donnée ou implémenter une interface donnée comme ceci:

public interface IEntity
{

}

public class Employee : IEntity
{
    public string FirstName { get; set; }
    public string LastName { get; set; }
    public int EmployeeID { get; set; }
}

public class Company : IEntity
{
    public string Name { get; set; }
    public string TaxID { get; set }
}

public class DataService<ENTITY, DATACONTEXT>
    where ENTITY : class, IEntity, new()
    where DATACONTEXT : DataContext, new()
{

    public void Create(List<ENTITY> entities)
    {
        using (DATACONTEXT db = new DATACONTEXT())
        {
            Table<ENTITY> table = db.GetTable<ENTITY>();

            foreach (ENTITY entity in entities)
                table.InsertOnSubmit (entity);

            db.SubmitChanges();
        }
    }
}

public class MyTest
{
    public void DoSomething()
    {
        var dataService = new DataService<Employee, MyDataContext>();
        dataService.Create(new Employee { FirstName = "Bob", LastName = "Smith", EmployeeID = 5 });
        var otherDataService = new DataService<Company, MyDataContext>();
            otherDataService.Create(new Company { Name = "ACME", TaxID = "123-111-2233" });

    }
}

Notez la réutilisation du même service étant donné les différents types dans la méthode DoSomething ci-dessus. Vraiment élégant!

Il y a beaucoup d'autres bonnes raisons d'utiliser des génériques pour votre travail, c'est ma préférée.

Dean Poulin
la source
5

Je les aime juste parce qu'ils vous donnent un moyen rapide de définir un type personnalisé (comme je les utilise de toute façon).

Ainsi, par exemple, au lieu de définir une structure composée d'une chaîne et d'un entier, puis d'avoir à implémenter tout un ensemble d'objets et de méthodes sur la façon d'accéder à un tableau de ces structures et ainsi de suite, vous pouvez simplement créer un dictionnaire

Dictionary<int, string> dictionary = new Dictionary<int, string>();

Et le compilateur / IDE fait le reste du gros du travail. Un dictionnaire en particulier vous permet d'utiliser le premier type comme clé (pas de valeurs répétées).

Tom Kidd
la source
5
  • Collections typées - même si vous ne souhaitez pas les utiliser, vous devrez probablement les gérer à partir d'autres bibliothèques, d'autres sources.

  • Typage générique dans la création de classe:

    classe publique Foo <T> {public T get () ...

  • Éviter le casting - J'ai toujours détesté des choses comme

    nouveau comparateur {public int compareTo (Object o) {if (o instanceof classIcareAbout) ...

Où vous recherchez essentiellement une condition qui ne devrait exister que parce que l'interface est exprimée en termes d'objets.

Ma réaction initiale aux génériques était similaire à la vôtre - «trop compliqué, trop compliqué». Mon expérience est qu'après les avoir utilisés pendant un moment, vous vous y habituez, et le code sans eux semble moins clairement spécifié et tout simplement moins confortable. En dehors de cela, le reste du monde java les utilise, vous devrez donc finir par utiliser le programme, non?


1
pour être précis en ce qui concerne l'évitement du casting: le casting se produit, selon la documentation de Sun, mais il est effectué par le compilateur. Vous évitez simplement d'écrire des casts dans votre code.
Demi

Peut-être, je semble être coincé dans une longue série d'entreprises qui ne veulent pas aller au-dessus de 1,4, alors qui sait, je pourrais prendre ma retraite avant d'arriver à 5. J'ai aussi pensé récemment que forger un "SimpleJava" pourrait être un concept intéressant --Java continuera d'ajouter des fonctionnalités et pour une grande partie du code que j'ai vu, ce ne sera pas une chose saine. Je traite déjà avec des gens qui ne peuvent pas coder une boucle - je détesterais les voir essayer d'injecter des génériques dans leurs boucles inutilement déroulées.
Bill K

@Bill K: Peu de gens ont besoin d'utiliser toute la puissance des génériques. Cela n'est nécessaire que lorsque vous écrivez une classe qui est elle-même générique.
Eddie

5

Pour donner un bon exemple. Imaginez que vous ayez une classe appelée Foo

public class Foo
{
   public string Bar() { return "Bar"; }
}

Exemple 1 Vous voulez maintenant avoir une collection d'objets Foo. Vous avez deux options, LIst ou ArrayList, qui fonctionnent toutes deux de la même manière.

Arraylist al = new ArrayList();
List<Foo> fl = new List<Foo>();

//code to add Foos
al.Add(new Foo());
f1.Add(new Foo());

Dans le code ci-dessus, si j'essaye d'ajouter une classe de FireTruck au lieu de Foo, l'ArrayList l'ajoutera, mais la liste générique de Foo provoquera la levée d'une exception.

Exemple deux.

Vous avez maintenant vos deux listes de tableaux et vous souhaitez appeler la fonction Bar () sur chacune. Puisque la liste ArrayList est remplie d'objets, vous devez les convertir avant de pouvoir appeler bar. Mais comme la liste générique de Foo ne peut contenir que des Foos, vous pouvez appeler Bar () directement sur ceux-ci.

foreach(object o in al)
{
    Foo f = (Foo)o;
    f.Bar();
}

foreach(Foo f in fl)
{
   f.Bar();
}

Je suppose que vous avez répondu avant de lire la question. Ce sont les deux cas qui ne m'aident pas vraiment beaucoup (décrits en question). Y a-t-il d'autres cas où cela fait quelque chose d'utile?
Bill K

4

N'avez-vous jamais écrit une méthode (ou une classe) où le concept clé de la méthode / classe n'était pas étroitement lié à un type de données spécifique des paramètres / variables d'instance (pensez liste chaînée, fonctions max / min, recherche binaire , etc.).

N'avez-vous jamais souhaité pouvoir réutiliser l'algorithme / code sans recourir à la réutilisation couper-coller ou compromettre le typage fort (par exemple, je veux une Listdes chaînes, pas une Listdes choses que j'espère être des chaînes!)?

C'est pourquoi vous devriez vouloir utiliser des génériques (ou quelque chose de mieux).


Je n'ai jamais eu à copier / coller la réutilisation pour ce genre de choses (Je ne sais pas comment cela se passerait même?) Vous implémentez une interface qui correspond à vos besoins et utilisez cette interface. Le cas rare où vous ne pouvez pas faire cela est lorsque vous écrivez un utilitaire pur (comme les collections), et pour ceux-ci (comme je l'ai décrit dans la question), cela n'a vraiment jamais été un problème. Je fais des compromis sur le typage fort - Tout comme vous devez le faire si vous utilisez la réflexion. Refusez-vous absolument d'utiliser la réflexion parce que vous ne pouvez pas avoir une frappe forte? (Si je dois utiliser la réflexion, je l'encapsule juste comme une collection).
Bill K

3

N'oubliez pas que les génériques ne sont pas seulement utilisés par les classes, ils peuvent également être utilisés par des méthodes. Par exemple, prenez l'extrait de code suivant:

private <T extends Throwable> T logAndReturn(T t) {
    logThrowable(t); // some logging method that takes a Throwable
    return t;
}

C'est simple, mais peut être utilisé de manière très élégante. La bonne chose est que la méthode renvoie tout ce qu'elle a été donné. Cela aide lorsque vous gérez des exceptions qui doivent être renvoyées à l'appelant:

    ...
} catch (MyException e) {
    throw logAndReturn(e);
}

Le fait est que vous ne perdez pas le type en le passant par une méthode. Vous pouvez lancer le type d'exception correct au lieu d'un simpleThrowable , ce qui serait tout ce que vous pourriez faire sans les génériques.

Ceci n'est qu'un simple exemple d'utilisation des méthodes génériques. Il y a pas mal d'autres choses intéressantes que vous pouvez faire avec des méthodes génériques. Le plus cool, à mon avis, est l'inférence de type avec des génériques. Prenons l'exemple suivant (tiré de Effective Java 2nd Edition de Josh Bloch):

...
Map<String, Integer> myMap = createHashMap();
...
public <K, V> Map<K, V> createHashMap() {
    return new HashMap<K, V>();
}

Cela ne fait pas grand-chose, mais cela réduit l'encombrement lorsque les types génériques sont longs (ou imbriqués, c'est-à-dire Map<String, List<String>>).


Je ne pense pas que ce soit vrai pour les exceptions. Cela me fait réfléchir, mais je suis sûr à 95% que vous obtiendriez exactement les mêmes résultats sans utiliser du tout des génériques (et un code plus clair). Les informations de type font partie de l'objet, pas vers quoi vous les convertissez. Je suis tenté d'essayer - peut-être que je le ferai demain au travail et que je reviendrai vers vous .. Dites-vous quoi, je vais essayer et si vous avez raison, je vais passer votre liste de messages et votez pour 5 de vos messages :) Sinon, vous voudrez peut-être considérer que la simple disponibilité des génériques vous a amené à compliquer votre code sans aucun avantage.
Bill K

Il est vrai que cela ajoute une complexité supplémentaire. Cependant, je pense que cela a une certaine utilité. Le problème est que vous ne pouvez pas jeter un ancien à Throwablepartir d'un corps de méthode qui a des exceptions déclarées spécifiques. L'alternative consiste soit à écrire des méthodes distinctes pour renvoyer chaque type d'exception, soit à effectuer le cast vous-même avec une méthode non générique qui renvoie un Throwable. Le premier est trop verbeux et assez inutile et le second n'obtiendra aucune aide du compilateur. En utilisant des génériques, le compilateur insérera automatiquement la distribution correcte pour vous. Alors, la question est: est-ce que cela vaut la complexité?
jigawot

Oh je comprends. Donc ça évite que le casting ne sorte ... bon point. Je te dois 5.
Bill K

2

Le principal avantage, comme le souligne Mitchel, est un typage fort sans avoir besoin de définir plusieurs classes.

De cette façon, vous pouvez faire des choses comme:

List<SomeCustomClass> blah = new List<SomeCustomClass>();
blah[0].SomeCustomFunction();

Sans génériques, vous auriez à lancer blah [0] sur le type correct pour accéder à ses fonctions.


Étant donné le choix, je préfère ne pas lancer plutôt que lancer.
MrBoJangles

Un typage fort est facilement le meilleur aspect des génériques à mon humble avis, surtout compte tenu de la vérification de type à la compilation qui le permet.
ljs

2

le jvm lance quand même ... il crée implicitement du code qui traite le type générique comme "Object" et crée des conversions à l'instanciation souhaitée. Les génériques Java ne sont que du sucre syntaxique.


2

Je sais que c'est une question C #, mais génériques sont également utilisés dans d'autres langages, et leur utilisation / objectifs sont assez similaires.

Les collections Java utilisent des génériques depuis Java 1.5. Ainsi, un bon endroit pour les utiliser est lorsque vous créez votre propre objet de type collection.

Un exemple que je vois presque partout est une classe Pair, qui contient deux objets, mais doit traiter ces objets de manière générique.

class Pair<F, S> {
    public final F first;
    public final S second;

    public Pair(F f, S s)
    { 
        first = f;
        second = s;   
    }
}  

Chaque fois que vous utilisez cette classe Pair, vous pouvez spécifier le type d'objets que vous souhaitez traiter et tout problème de conversion de type apparaîtra au moment de la compilation, plutôt qu'au moment de l'exécution.

Les génériques peuvent également avoir leurs limites définies avec les mots-clés «super» et «étend». Par exemple, si vous voulez traiter un type générique mais que vous voulez vous assurer qu'il étend une classe appelée Foo (qui a une méthode setTitle):

public class FooManager <F extends Foo>{
    public void setTitle(F foo, String title) {
        foo.setTitle(title);
    }
}

Bien que cela ne soit pas très intéressant en soi, il est utile de savoir que chaque fois que vous traitez avec un FooManager, vous savez qu'il gérera les types MyClass, et que MyClass étend Foo.


2

Dans la documentation Sun Java, en réponse à "pourquoi devrais-je utiliser des génériques?":

"Generics fournit un moyen pour vous de communiquer le type d'une collection au compilateur, afin qu'il puisse être vérifié. Une fois que le compilateur connaît le type d'élément de la collection, le compilateur peut vérifier que vous avez utilisé la collection de manière cohérente et peut insérer les casts corrects sur les valeurs retirées de la collection ... Le code utilisant des génériques est plus clair et plus sûr .... le compilateur peut vérifier au moment de la compilation que les contraintes de type ne sont pas violées au moment de l'exécution [c'est moi qui souligne]. programme compile sans avertissement, nous pouvons affirmer avec certitude qu'il ne lancera pas d'exception ClassCastException au moment de l'exécution. L'effet net de l'utilisation de génériques, en particulier dans les grands programmes, est une meilleure lisibilité et robustesse .


Je n'ai jamais trouvé de cas où le typage générique des collections aurait aidé mon code. Mes collections sont étroitement délimitées. C'est pourquoi j'ai formulé ceci: «Peut-il m'aider?». En guise de question secondaire, est-ce que vous dites qu'avant de commencer à utiliser des génériques, cela vous arrivait parfois? (mettre le mauvais type d'objet dans votre collection). Cela me semble être une solution sans réel problème, mais peut-être que j'ai de la chance.
Bill K

@Bill K: si le code est étroitement contrôlé (c'est-à-dire uniquement utilisé par vous), vous ne rencontrerez peut-être jamais cela comme un problème. C'est génial! Les plus grands risques sont dans les grands projets sur lesquels plusieurs personnes travaillent, l'OMI. C'est un filet de sécurité et une «meilleure pratique».
Demi

@Bill K: Voici un exemple. Supposons que vous ayez une méthode qui renvoie une ArrayList. Disons que ArrayList n'est pas une collection générique. Le code de quelqu'un prend cette ArrayList et tente d'effectuer une opération sur ses éléments. Le seul problème est que vous avez rempli la ArrayList avec BillTypes et que le consommateur essaie de fonctionner sur ConsumerTypes. Cela se compile, mais explose pendant l'exécution. Si vous utilisez des collections génériques, vous aurez à la place des erreurs de compilation, qui sont beaucoup plus agréables à gérer.
Demi

@Bill K: J'ai travaillé avec des personnes aux niveaux de compétence très différents. Je n'ai pas le luxe de choisir avec qui je partage un projet. Ainsi, la sécurité du type est très importante pour moi. Oui, j'ai trouvé (et corrigé) des bogues en convertissant du code pour utiliser Generics.
Eddie

1

Les génériques vous permettent de créer des objets fortement typés, mais vous n'avez pas à définir le type spécifique. Je pense que le meilleur exemple utile est la liste et les classes similaires.

En utilisant la liste générique, vous pouvez avoir une liste de liste comme vous le souhaitez et vous pouvez toujours référencer le typage fort, vous n'avez pas à convertir ou quelque chose comme vous le feriez avec un tableau ou une liste standard.


1

Les génériques vous permettent d'utiliser un typage fort pour les objets et les structures de données qui devraient pouvoir contenir n'importe quel objet. Il élimine également les typecasts fastidieux et coûteux lors de la récupération d'objets à partir de structures génériques (boxing / unboxing).

Un exemple qui utilise les deux est une liste chaînée. A quoi servirait une classe de liste chaînée si elle ne pouvait utiliser que l'objet Foo? Pour implémenter une liste liée pouvant gérer n'importe quel type d'objet, la liste liée et les nœuds d'une classe interne de nœud hypothétique doivent être génériques si vous souhaitez que la liste ne contienne qu'un seul type d'objet.


1

Si votre collection contient des types de valeur, ils n'ont pas besoin d'être encadrés / déballés en objets lorsqu'ils sont insérés dans la collection afin que vos performances augmentent considérablement. Des add-ons sympas tels que resharper peuvent générer plus de code pour vous, comme les boucles foreach.


1

Un autre avantage de l'utilisation des génériques (en particulier avec les collections / listes) est la vérification du type à la compilation. Ceci est vraiment utile lorsque vous utilisez une liste générique au lieu d'une liste d'objets.


1

La principale raison est qu'ils fournissent la sécurité de type

List<Customer> custCollection = new List<Customer>;

par opposition à,

object[] custCollection = new object[] { cust1, cust2 };

à titre d'exemple simple.


Tapez sécurité, une discussion en soi. Peut-être que quelqu'un devrait poser une question à ce sujet.
MrBoJangles

Ouais mais à quoi fais-tu allusion? Tout l'intérêt de Type Safety?
Vin

1

En résumé, les génériques vous permettent de spécifier plus précisément ce que vous comptez faire (typage plus fort).

Cela présente plusieurs avantages pour vous:

  • Parce que le compilateur en sait plus sur ce que vous voulez faire, il vous permet d'omettre beaucoup de conversion de type car il sait déjà que le type sera compatible.

  • Cela vous permet également d'obtenir des commentaires plus tôt sur les corrections de votre programme. Les choses qui auparavant auraient échoué à l'exécution (par exemple parce qu'un objet ne pouvait pas être transtypé dans le type souhaité) échouent maintenant au moment de la compilation et vous pouvez corriger l'erreur avant que votre service de test ne dépose un rapport de bogue cryptique.

  • Le compilateur peut faire plus d'optimisations, comme éviter la boxe, etc.


1

Quelques éléments à ajouter / développer (en parlant du point de vue .NET):

Les types génériques vous permettent de créer des classes et des interfaces basées sur les rôles. Cela a déjà été dit en termes plus basiques, mais je trouve que vous commencez à concevoir votre code avec des classes qui sont implémentées de manière indépendante du type - ce qui aboutit à un code hautement réutilisable.

Les arguments génériques sur les méthodes peuvent faire la même chose, mais ils aident aussi à appliquer le principe "Tell Don't Ask" au casting, c'est-à-dire "donnez-moi ce que je veux, et si vous ne pouvez pas, dites-moi pourquoi".


1

Je les utilise par exemple dans un GenericDao implémenté avec SpringORM et Hibernate qui ressemble à ceci

public abstract class GenericDaoHibernateImpl<T> 
    extends HibernateDaoSupport {

    private Class<T> type;

    public GenericDaoHibernateImpl(Class<T> clazz) {
        type = clazz;
    }

    public void update(T object) {
        getHibernateTemplate().update(object);
    }

    @SuppressWarnings("unchecked")
    public Integer count() {
    return ((Integer) getHibernateTemplate().execute(
        new HibernateCallback() {
            public Object doInHibernate(Session session) {
                    // Code in Hibernate for getting the count
                }
        }));
    }
  .
  .
  .
}

En utilisant des génériques, mes implémentations de ces DAO obligent le développeur à leur transmettre uniquement les entités pour lesquelles ils sont conçus en sous-classant simplement le GenericDao

public class UserDaoHibernateImpl extends GenericDaoHibernateImpl<User> {
    public UserDaoHibernateImpl() {
        super(User.class);     // This is for giving Hibernate a .class
                               // work with, as generics disappear at runtime
    }

    // Entity specific methods here
}

Mon petit framework est plus robuste (avec des choses comme le filtrage, le chargement paresseux, la recherche). Je viens de simplifier ici pour vous donner un exemple

Moi, comme Steve et vous, j'ai dit au début "Trop compliqué et compliqué" mais maintenant je vois ses avantages


1

Des avantages évidents tels que «sécurité de type» et «pas de coulée» sont déjà mentionnés, alors je peux peut-être parler d'autres «avantages» qui, j'espère, aideront.

Tout d'abord, les génériques sont un concept indépendant du langage et, à mon avis, cela pourrait avoir plus de sens si vous pensez au polymorphisme régulier (à l'exécution) en même temps.

Par exemple, le polymorphisme tel que nous le savons de la conception orientée objet a une notion d'exécution dans laquelle l'objet appelant est déterminé au moment de l'exécution au fur et à mesure de l'exécution du programme et la méthode appropriée est appelée en conséquence en fonction du type d'exécution. En génériques, l'idée est quelque peu similaire mais tout se passe au moment de la compilation. Qu'est-ce que cela signifie et comment vous en servez-vous?

(Tenons-nous en aux méthodes génériques pour le garder compact) Cela signifie que vous pouvez toujours avoir la même méthode sur des classes séparées (comme vous l'avez fait précédemment dans les classes polymorphes) mais cette fois, elles sont générées automatiquement par le compilateur en fonction des types définis au moment de la compilation. Vous paramétrez vos méthodes sur le type que vous donnez au moment de la compilation. Donc, au lieu d'écrire les méthodes à partir de zéro pour chaque type vous avez comme vous le faites dans le polymorphisme d'exécution ( méthode), vous laissez les compilateurs faire le travail pendant la compilation. Cela présente un avantage évident puisque vous n'avez pas besoin de déduire tous les types possibles qui pourraient être utilisés dans votre système, ce qui le rend beaucoup plus évolutif sans changement de code.

Les cours fonctionnent à peu près de la même manière. Vous paramétrez le type et le code est généré par le compilateur.

Une fois que vous avez eu l'idée de "temps de compilation", vous pouvez utiliser des types "bornés" et restreindre ce qui peut être passé en tant que type paramétré via les classes / méthodes. Ainsi, vous pouvez contrôler ce qui doit être traversé, ce qui est puissant, en particulier si vous utilisez un framework par d'autres personnes.

public interface Foo<T extends MyObject> extends Hoo<T>{
    ...
}

Personne ne peut maintenant définir autre chose que MyObject.

De plus, vous pouvez "appliquer" des contraintes de type sur vos arguments de méthode, ce qui signifie que vous pouvez vous assurer que les deux arguments de votre méthode dépendent du même type.

public <T extends MyObject> foo(T t1, T t2){
    ...
}   

J'espère que tout cela a du sens.



0

L'utilisation de génériques pour les collections est simple et propre. Même si vous le faites partout ailleurs, le gain des collections est une victoire pour moi.

List<Stuff> stuffList = getStuff();
for(Stuff stuff : stuffList) {
    stuff.do();
}

contre

List stuffList = getStuff();
Iterator i = stuffList.iterator();
while(i.hasNext()) {
    Stuff stuff = (Stuff)i.next();
    stuff.do();
}

ou

List stuffList = getStuff();
for(int i = 0; i < stuffList.size(); i++) {
    Stuff stuff = (Stuff)stuffList.get(i);
    stuff.do();
}

Cela seul vaut le «coût» marginal des génériques, et vous n'avez pas besoin d'être un gourou générique pour utiliser cela et obtenir de la valeur.


1
Sans génériques, vous pouvez faire: List stuffList = getStuff (); pour (trucs d'objet: stuffList) {((trucs) trucs) .doStuff (); }, ce qui n'est pas très différent.
Tom Hawtin - tackline

la source
Sans génériques, je fais stuffList.doAll (); J'ai dit que j'avais toujours une classe wrapper qui est exactement l'endroit pour du code comme ça. Sinon, comment éviter de copier cette boucle ailleurs? Où le mettez-vous pour le réutiliser? La classe wrapper aura probablement une sorte de boucle, mais dans cette classe, puisque cette classe est tout à propos de "The stuff", utiliser une boucle castée pour comme Tom suggéré ci-dessus est assez clair / auto-documenté.
Bill K
@Jherico, c'est vraiment intéressant. Je me demande s'il y a une raison pour laquelle vous ressentez cela, et si c'est ce que je ne comprends pas - qu'il y a quelque chose dans la nature des gens qui trouve n'importe quel casting pour quelque raison que ce soit obscène et est prêt à aller à des longueurs non naturelles (comme ajout d'une syntaxe beaucoup plus complexe) pour l'éviter. Pourquoi "Si vous devez lancer quelque chose, vous avez déjà perdu"?
Bill K
@Jherico: selon la documentation de Sun, les génériques permettent au compilateur de savoir vers quoi convertir des objets. Puisque le compilateur effectue le casting pour les génériques, plutôt que l'utilisateur, cela change-t-il votre déclaration?
Demi
J'ai converti le code d'avant Java 1.5 en Generics, et dans le processus j'ai trouvé des bogues où le mauvais type d'objet a été placé dans une collection. Je tombe également dans le camp "si vous devez lancer vous avez perdu", car si vous devez lancer manuellement, vous courez le risque d'une ClassCastException. Avec le compilateur Generics il fait invisiblement pour vous, mais la chance d'un ClassCastException est ainsi réduite en raison de la sécurité de type. Oui, sans Generics, vous pouvez utiliser Object, mais c'est une odeur de code terrible pour moi, la même chose que "throws Exception".
Eddie
0

Les génériques vous donnent également la possibilité de créer plus d'objets / méthodes réutilisables tout en fournissant un support spécifique au type. Vous gagnez également beaucoup de performances dans certains cas. Je ne connais pas la spécification complète sur les Java Generics, mais dans .NET, je peux spécifier des contraintes sur le paramètre Type, comme Implements a Interface, Constructor et Derivation.

Eric Schneider
la source
0
  1. Permettre aux programmeurs d'implémenter des algorithmes génériques - En utilisant des génériques, les programmeurs peuvent implémenter des algorithmes génériques qui fonctionnent sur des collections de différents types, peuvent être personnalisés et sont de type sûr et plus faciles à lire.

  2. Vérifications de type plus fortes au moment de la compilation - Un compilateur Java applique une vérification de type forte au code générique et émet des erreurs si le code enfreint la sécurité de type. La correction des erreurs de compilation est plus facile que la correction des erreurs d'exécution, qui peuvent être difficiles à trouver.

  3. Élimination des moulages.

Cendre
la source