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.
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.
Réponses:
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.
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 ClassCastException
s 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 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 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 10
est automatiquement converti en un Integer
avec la valeur de 10
. (Et pareil pour 42
). Ensuite, cela Integer
est jeté dans le Set
qui est connu pour détenir l' Integer
art. Essayer de lancer un String
provoquerait une erreur de compilation.
Ensuite, la boucle for for-each prend les trois:
for (int i : set) {
total += i;
}
Tout d'abord, les Set
conteneurs Integer
s sont utilisés dans une boucle for-each. Chaque élément est déclaré comme étant un int
et cela est autorisé car le Integer
est 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 Integer
contenus 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' Set
exemple, 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' Set
exemple 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 Object
est 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 int
valeur primitive 10
et 42
sont autoboxed en Integer
objets, qui sont ajoutés au Set
. Cependant, gardez à l'esprit que les Integer
objets sont traités en tant que Object
s, car il n'y a aucune information de type pour aider le compilateur à savoir à quel type il 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 que le Set
implémente l' Iterable
interface, qui renvoie une Iterator
information de type avec, si elle est présente. (Iterator<T>
, c'est.)
Cependant, comme il n'y a pas d'informations de type, le Set
renverra un Iterator
qui retournera les valeurs dans Set
as Object
s, 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 Object
est récupéré à partir de Set
, il doit être converti Integer
manuellement 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 ClassCastException
attend, mais je m'éloigne du sujet ...)
Le Integer
est maintenant déballé dans un int
et autorisé à effectuer l'ajout dans la int
variable 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.
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 NullPointerException
que 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.
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 List
dont les éléments sont T
s . Il en List
va 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, 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
il B
peut s'agir de tous les types que vous souhaitez, à condition de fournir une fonction f
qui 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 extends
mot - 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 Widget
prolongée par FooWidget
, BarWidget
et BazWidget
, avec les génériques , vous pouvez avoir une seule classe générique Widget<A>
qui prend Foo
, Bar
ou Baz
dans son constructeur pour vous donner Widget<Foo>
, Widget<Bar>
et Widget<Baz>
.
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.
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.
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).
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?
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();
}
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 List
des chaînes, pas une List
des 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).
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>>
).
Throwable
partir 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é?
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.
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.
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.
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 .
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.
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.
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.
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.
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.
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.
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".
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
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.
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/ .
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.
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.
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.
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.
Élimination des moulages.