J'ai pensé offrir ce softball à quiconque voudrait le sortir du parc. Quels sont les génériques, quels sont les avantages des génériques, pourquoi, où, comment dois-je les utiliser? S'il vous plaît, gardez-le assez basique. Merci.
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 reformuler 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 voulez pas utiliser la collection brute pour une raison quelconque)
class MyList<T> {
T get(int index) { ... }
}
Je suis désormais 3 fois plus efficace et je n'ai plus qu'à conserver une copie. Pourquoi NE VOULEZ-VOUS PAS conserver moins de code?
Cela est également vrai pour les classes non-collection comme un 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 de type sécurisé?
Je ne.
Ne pas avoir besoin de transtyper est l'un des plus grands avantages de Java génériques , car il effectuera une vérification de type lors de la compilation- Cela réduira la possibilité de ClassCastException
s qui peuvent ê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 les génériques, cela me donne mal à la tête. Je trouve que la meilleure partie de Java pour être 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 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 quand j'ai rencontré des génériques, j'ai senti que c'était plus de code d'écrire, et je ne 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 à l'écriture de code avec des génériques, en particulier à l'achèvement du 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, c'est beaucoup à taper juste pour faire un nouveau HashMap
. Cependant, en réalité, je n'avais qu'à taper autant avant qu'Eclipse ne sache ce dont j'avais besoin:
Map<String, Integer> m = new Ha
Ctrl+Space
Certes, j'avais besoin de sélectionner HashMap
dans une liste de candidats, mais en gros, le IDE savait quoi ajouter, y compris les types génériques. Avec les bons outils, l'utilisation de génériques n'est pas pas trop mal.
De plus, comme les types sont connus, lors de la récupération d'éléments de la collection générique, le IDE agira comme si cet objet était déjà un objet de son type déclaré - il n'est pas nécessaire de transtyper pour le IDE pour savoir quel est le type de l'objet.
Un avantage clé des génériques vient de la façon dont ils fonctionnent bien avec les nouvelles fonctionnalités Java 5. Voici un exemple de lancer entiers dans un Set
et calcul de 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, il y a trois nouvelles fonctionnalités Java 5 présentes:
Premièrement, les génériques et la mise en boîte automatique des primitives permettent les lignes suivantes:
set.add(10);
set.add(42);
L'entier 10
est placé automatiquement dans un Integer
avec la valeur de 10
. (Et pareil pour 42
). Ensuite, ce Integer
est jeté dans le Set
qui est connu pour contenir Integer
s. Essayer de lancer un String
provoquerait une erreur de compilation.
Ensuite, pour chaque boucle prend les trois:
for (int i : set) {
total += i;
}
Tout d'abord, les Set
contenant Integer
s sont utilisés dans une boucle for-each. Chaque élément est déclaré être un int
et cela est autorisé car le Integer
est décompressé dans la primitive int
. Et le fait que ce déballage se produise est connu parce que les génériques ont été utilisés pour spécifier qu'il y avait Integer
s dans le Set
.
Les génériques peuvent être le ciment 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 assez intelligents pour vous aider avec de bonnes suggestions, donc en général, il n'y aura pas beaucoup plus de frappe.
Et franchement, comme on peut le voir dans l'exemple Set
, 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
Ce qui suit est une illustration de l'exemple Set
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 contrôlé au moment de la compilation.)
Lors de l'utilisation de collections non génériques, les types entrés dans la collection sont des objets de type Object
. Par conséquent, dans cet exemple, un Object
est ce qui est add
ed 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 en cours de mise en boîte automatique dans les objets Integer
, qui sont ajoutés aux Set
. Cependant, gardez à l'esprit que les objets Integer
sont traités comme Object
s, car il n'y a aucune information de type pour aider le compilateur à savoir à quel type le Set
doit s'attendre.
for (Object o : set) {
C'est la partie qui est cruciale. La raison pour laquelle la boucle for-each fonctionne est parce que le Set
implémente l'interface Iterable
, qui renvoie un Iterator
avec les informations de type, le cas échéant. (Iterator<T>
, C'est.)
Cependant, puisqu'il n'y a aucune information de type, le Set
renverra un Iterator
qui renverra les valeurs dans le Set
en tant que Object
s, et c'est pourquoi l'élément récupéré dans la boucle for-each must doit être de type Object
.
Maintenant que le Object
est récupéré du Set
, il doit être converti en Integer
manuellement pour effectuer l'ajout:
total += (Integer)o;
Ici, un transtypage est effectué à partir d'un Object
vers un Integer
. Dans ce cas, nous savons que cela fonctionnera toujours, mais le transtypage manuel me fait toujours sentir qu'il s'agit d'un code fragile qui pourrait être endommagé si une modification mineure était effectuée ailleurs. (Je pense que chaque transtypage est un ClassCastException
qui attend de se produire, mais je m'éloigne du sujet ...)
Le Integer
est désormais décompressé dans un int
et autorisé à effectuer l'ajout dans la variable int
total
.
J'espère que je pourrais 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 d'écrire du code avec des génériques. Et , à mon avis, pour tirer pleinement parti des nouvelles fonctionnalités de Java 5, il convient de se pencher sur les génériques, si au moins, permet des vérifications au moment de la compilation pour empêcher les typecasts invalides de lever des exceptions lors de l'exécution.
Si vous deviez rechercher la base de données de bogues Java juste avant la sortie de la version 1.5, vous trouveriez sept fois plus de bogues avec NullPointerException
que ClassCastException
. Il ne semble 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 du code, 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.
Les génériques dans Java facilite polymorphisme paramétrique . Au moyen des paramètres de type, vous pouvez passer des arguments aux types. Tout comme une méthode comme String foo(String s)
en modélise certains comportement, non seulement pour une chaîne particulière, mais pour toute chaîne s
, donc un type comme List<T>
modélise certains comportements, non seulement pour un type spécifique, mais pour tout type . List<T>
indique que pour tout type T
, il existe 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 deux types, A
et B
, il existe une fonction (appelée f
) qui prend un A
et retourne un B
. Lorsque vous implémentez cette interface, A
et B
peuvent être de n'importe quel type, aussi longtemps car vous fournissez une fonction f
qui prend la première et retourne 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 mot clé extends
. Avec les génériques, nous pouvons en fait supprimer le sous-classement et utiliser à la place le polymorphisme paramétrique. 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 ceci:
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 d'utiliser 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 sur des types, mais vous ne pouvez pas abstraire sur des constructeurs de type, 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 de Java, ici.
Une grande victoire 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 qui sont difficiles à comprendre individuellement sans regarder la hiérarchie entière.
Avant les génériques, vous pourriez avoir des classes comme Widget
étendues 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>
.
Les génériques évitent les performances de boxe et de déballage. Fondamentalement, 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 besoin de boxer vers/depuis l'objet.
Collections typées - même si vous ne voulez pas les utiliser, vous devrez probablement les traiter à partir d'autres bibliothèques, d'autres sources.
Saisie générique en 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 vérifiez 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 semblable à la vôtre - "trop salissante, trop compliquée". D'après mon expérience, après les avoir utilisés un peu, vous vous y habituez et le code sans eux se sent moins clairement spécifié et juste moins confortable. Mis à part cela, le reste du monde Java les utilise donc vous allez devoir finir par utiliser le programme, non?
Pour donner un bon exemple. Imaginez que vous ayez une classe appelée Foo
public class Foo
{
public string Bar() { return "Bar"; }
}
Exemple 1 Maintenant, vous voulez avoir une collection d'objets Foo. Vous avez deux options, LIst ou ArrayList, qui fonctionnent toutes les deux de manière similaire.
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'essaie d'ajouter une classe de FireTruck au lieu de Foo, 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. Étant donné que la ArrayList est remplie d'objets, vous devez les lancer 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 les aime juste parce qu'ils vous donnent un moyen rapide de définir un type personnalisé (comme je les utilise quand même).
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 un ensemble complet d'objets et de méthodes pour accéder à un tableau de ces structures, etc., 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).
Le meilleur avantage pour Generics 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. (I.E Linq aux opérations SQL).
Avec les génériques, vous pouvez créer une classe qui pourra fonctionner en fonction de n'importe quel type héritant 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 existe de nombreuses autres bonnes raisons d'utiliser des génériques pour votre travail, c'est ma préférée.
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 liée, fonctions max/min, recherche binaire , etc.).
N'avez-vous jamais souhaité pouvoir réutiliser l'algorithme/le code sans recourir à la réutilisation couper-coller ou compromettre le typage fort (par exemple, je veux un List
de chaînes, pas un List
de ce que j'espère sont des chaînes!)?
C'est pourquoi vous devriez vouloir utiliser des génériques (ou quelque chose de mieux).
N'oubliez pas que les génériques ne sont pas seulement utilisés par les classes, ils peuvent également être utilisés par les 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é très élégamment. La bonne chose est que la méthode retourne tout ce qu'elle a été donnée. Cela vous 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 lever le type d'exception correct au lieu d'un simple Throwable
, ce qui serait tout ce que vous pourriez faire sans génériques.
Ceci est juste un exemple simple d'une utilisation pour les 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 le type inférant avec des génériques. Prenons l'exemple suivant (tiré de Josh Bloch's Effective Java 2nd Edition):
...
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>>
).
De la documentation Sun Java Java, en réponse à "pourquoi devrais-je utiliser des génériques?":
"Les génériques vous permettent 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 conversions correctes 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 à run time [soulignement mine]. Parce que le programme compile sans avertissements, nous pouvons affirmer avec certitude qu'il ne lèvera pas une 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 lisibilité et une robustesse améliorées . [c'est moi qui souligne] "
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 transitions à l'instanciation souhaitée. Java les génériques ne sont que du sucre syntaxique.
Le principal avantage, comme le souligne Mitchel, est le 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 devez transtyper blah [0] au type correct pour accéder à ses fonctions.
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 génériques depuis Java 1.5. Donc, 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 qu'elle traite et tous les problèmes de transtypage de type s'afficheront au moment de la compilation plutôt qu'à 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 souhaitez 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 qu'il 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.
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 forcent 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 (avoir des choses comme le filtrage, le chargement différé, la recherche). Je viens de simplifier ici pour vous donner un exemple
Comme Steve et vous, j'ai dit au début "Trop désordonné et compliqué" mais maintenant je vois ses avantages
Les génériques vous permettent de créer des objets fortement typés, mais vous n'avez pas besoin de 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 de liste tout ce que vous voulez 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.
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 transformations typographiques fastidieuses et coûteuses 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. À quoi servirait une classe de liste liée si elle ne pouvait utiliser que l'objet Foo? Pour implémenter une liste liée qui peut gérer tout 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.
Des avantages évidents comme la "sécurité du type" et "pas de coulée" sont déjà mentionnés, alors je peux peut-être parler d'autres "avantages" qui, je l'espère, vous 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 connaissons dans la conception orientée objet a une notion d'exécution dans laquelle l'objet appelant est compris lors de l'exécution pendant l'exécution du programme et la méthode appropriée est appelée en conséquence en fonction du type d'exécution. Dans les 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 l'utilisez-vous?
(Restons avec des méthodes génériques pour le garder compact) Cela signifie que vous pouvez toujours avoir la même méthode sur des classes distinctes (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. Ainsi, au lieu d'écrire les méthodes à partir de zéro pour chaque type unique que vous avez comme vous le faites dans le polymorphisme d'exécution (remplacement de méthode), vous laissez les compilateurs faire le travail pendant la compilation. Cela a un avantage évident car 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 l'idée du "temps de compilation", vous pouvez utiliser des types "bornés" et restreindre ce qui peut être passé en tant que type paramétré via des classes/méthodes. Ainsi, vous pouvez contrôler ce qui doit être traversé, ce qui est une chose puissante, en particulier si vous avez un cadre consommé par d'autres personnes.
public interface Foo<T extends MyObject> extends Hoo<T>{
...
}
Personne ne peut définir autre chose que MyObject maintenant.
En outre, vous pouvez "appliquer" des contraintes de type sur vos arguments de méthode, ce qui signifie que vous pouvez vous assurer que vos deux arguments de 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.
Un autre avantage de l'utilisation des génériques (en particulier avec les collections/listes) est que vous obtenez la vérification du type de compilation. C'est vraiment utile lorsque vous utilisez une liste générique au lieu d'une liste d'objets.
Si votre collection contient des types de valeur, ils n'ont pas besoin de mettre en boîte/décompresser les objets lorsqu'ils sont insérés dans la collection afin que vos performances augmentent considérablement. Les modules complémentaires sympas comme resharper peuvent générer plus de code pour vous, comme les boucles foreach.
La raison la plus importante est qu'ils fournissent Type de sécurité
List<Customer> custCollection = new List<Customer>;
par opposition à,
object[] custCollection = new object[] { cust1, cust2 };
comme exemple simple.
En résumé, les génériques vous permettent de spécifier plus précisément ce que vous avez l'intention de faire (frappe plus forte).
Cela présente plusieurs avantages pour vous:
Parce que le compilateur sait plus sur ce que vous voulez faire, il vous permet d'omettre beaucoup de transtypage 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 corrects de votre programme. Des choses qui auparavant auraient échoué au moment de l'exécution (par exemple parce qu'un objet ne pouvait pas être casté dans le type souhaité), échouent maintenant au moment de la compilation et vous pouvez corriger l'erreur avant que votre service de test dépose un rapport de bogue cryptique.
Le compilateur peut faire plus d'optimisations, comme éviter la boxe, etc.
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 simples, 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 se traduit par un code hautement réutilisable.
Les arguments génériques sur les méthodes peuvent faire la même chose, mais ils aident également à appliquer le principe "Tell Don't Ask" au casting, c'est-à-dire "donnez-moi ce que je veux, et si vous ne le pouvez pas, dites-moi pourquoi".
L'utilisation de génériques pour les collections est simple et propre. Même si vous jouez dessus 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 l'utiliser et obtenir de la valeur.
Les génériques vous donnent également la possibilité de créer des objets/méthodes plus 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 des génériques Java, mais en .NET, je peux spécifier des contraintes sur le paramètre Type, comme implémente une interface, un constructeur et une dérivation.
J'ai donné une fois une conférence sur ce sujet. Vous pouvez trouver mes diapositives, mon code et mon enregistrement audio sur http://www.adventuresinsoftware.com/generics/ .