Introduction to Java Generics
Quêtes
Introduction aux generics en Java
Nous avons déjà créé nous-mêmes au moins une List simple pour une Class spécifique pour commencer, même si Java fournit déjà des classes dans ce but. À l'époque où le code des classes List fournies par Java a été écrit, les programmeurs ne pouvaient pas connaître les classes pour lesquelles leur liste allait être utilisée. Comme toute classe dérive de java.lang.Object, les premières versions de Java utilisaient simplement cette classe pour représenter les éléments de la List. Cela pose certains problèmes que les generics (types génériques) de Java résolvent.

Les premières versions de Java n'incluaient pas les generics. En réalité, une approche similaire utilisée dans le langage C++, appelée templates (modèles génériques), avait été explicitement rejetée car on pensait qu'elle ajoutait une complexité inutile au langage Java. Les generics ont été introduits avec Java 1.5, ou en abrégé Java 5, dont le nom de code était 'Tiger', en septembre 2004.
🤓 À la fin de cette quête, tu sauras :
✅ Comprendre pourquoi on utilise les generics ✅ Prendre conscience des limites et des difficultés liées à l'utilisation des generics
👉 Pourquoi nous avons besoin des generics
Prends une liste simple comme présentée ci-dessous
public class FixedSizeList {
private Object[] items = new Object[5];
public void add(Object object) { /* ... */ }
public Object get(final int index) { /* ... */ }
}
Comme tu peux le voir, la méthode add() de cette liste accepte tout type d'Object, puisque tout type d'Object dérive de java.lang.Object. Cela transforme notre liste en un conteneur polyvalent.
Maintenant, regarde la méthode get(). Quand tu veux accéder à un élément que tu as mis dans cette liste, la déclaration a un type de retour Object. Il est impossible de savoir quel est le type concret de la valeur retournée. Le seul point commun, le plus petit dénominateur commun qui soit clair, c'est que la valeur retournée est un java.lang.Object, puisque toute classe dérive de cette classe en Java.
Pour résoudre ce problème, tu pourrais utiliser un cast (conversion de type) vers la classe concrète
// ...
Apple apple1 = new Apple();
fixedSizedList.add(apple1);
Object result = fixedSizedList.get(0);
Apple apple2 = (Apple) result;
Cela fonctionnera tant que tu es sûr à 100 % que la liste ne contiendra jamais que des pommes. Souviens-toi que la méthode add() acceptera n'importe quoi, donc il est facile de mettre n'importe quoi dans la liste.
Pour résoudre ce problème, il faudrait réécrire la FixedSizeList et utiliser la classe Apple pour les éléments. Là encore, cela ne résoudra les problèmes que pour les pommes, avec une FixedSizeListForApples. Pour une poire, il faudrait écrire une autre liste, et pour chaque autre classe il faudrait encore écrire une autre classe de liste. Cela représente beaucoup de travail.
La solution à ce problème consiste à rendre le type de l'élément plus flexible, en étendant le langage pour autoriser des paramètres dans la déclaration de la classe. Ces paramètres fonctionnent un peu comme les paramètres des fonctions, mais à un niveau différent. Ils sont utilisés par le compilateur au moment de la compilation, alors que les paramètres des fonctions fonctionnent à l'exécution.
Pour ajouter un paramètre pour les generics, le paramètre de type est ajouté à la classe entre chevrons
// ...
class GenericFixedSizeList<T> { /* ... */ }
C'est comparable aux méthodes, où le paramètre de type est ajouté entre parenthèses
// ...
void someMethod( Integer i) { /* ... */ }
Il y a une différence subtile ici. Le paramètre de type pour les generics n'a pas de type lui-même, car il est clair qu'il s'agit d'un type, ou plus simplement, du nom d'une classe.
Partout où le type java.lang.Object est utilisé dans la classe elle-même, il doit être remplacé par le nom du paramètre de type générique.
public class FixedSizeList<T> {
private T[] items = /* ... */
public void add(T object) { /* ... */ }
public T get(final int index) { /* ... */ }
}
Pour utiliser cette classe générique avec notre classe Apple, nous devons fournir le paramètre de type au moment où nous créons une liste.
FixedSizedList<Apple> fixedSizedList = new FixedSizedList<Apple>();
Il y a une chose importante à garder en tête
Le paramètre de type est résolu au moment de la compilation.
Pour accéder aux éléments de la liste générique, nous n'avons plus besoin de cast.
// ...
Apple apple1 = new Apple();
fixedSizedList.add(apple);
Apple apple2 = fixedSizedList.get(0);
Souviens-toi d'une chose, comme d'un mantra
Le cast, c'est mauvais
Évite-les quand tu peux, et dans la plupart des cas, tu le peux.
Tu peux regarder cette vidéo pour en apprendre davantage sur les generics en Java.
Conclusion
Avec cette introduction aux generics en Java, nous avons créé une classe de collection générique qui va nous faire gagner beaucoup de temps. Au lieu d'écrire un grand nombre de classes spécifiques pour des tâches répétitives, nous pouvons maintenant écrire une seule classe et la réutiliser. Il y a plusieurs choses à prendre en compte quand on utilise les generics en Java. Regarde la Cheat Sheet (fiche mémo) suivante pour plus de détails.
Tu peux utiliser ton moteur de recherche pour trouver une cheat sheet qui correspond à tes préférences de couleur ou qui propose une description plus détaillée.
🔬 Exercice
Tu pourrais utiliser l'opérateur instanceof pour sécuriser un cast. Essaie de résoudre le problème pour une liste non générique avec trois types concrets (Apple, Pear, Banana) et compare le résultat avec l'utilisation d'une classe générique.
Regarde le Composite Pattern (patron de conception composite) qui est lié aux generics, et vois comment il pourrait servir d'alternative pour résoudre le problème.
Est-ce que l'utilisation d'une FixedSizedListOfFruit résoudra le problème, et pourquoi ou pourquoi pas ?
Découvre pourquoi nous ne pouvons pas écrire new T[5] pour créer le tableau qui contient les éléments. Tu dois fournir une solution pour créer une nouvelle instance quel que soit le type générique T, afin de terminer cette quête.
☝️ Résumé
Cette quête a introduit les generics en Java, la raison de les utiliser et une implémentation simple
💪 Défi
Le défi de cette quête est de créer une liste générique et d'écrire des tests unitaires pour cette classe
Choses à surveiller :
- Fournis un test avec des valeurs null
🧐 Critères d'acceptation
⭙ Tu as écrit une classe de liste générique ⭙ Tu as des tests unitaires pour vérifier que tout fonctionne ⭙ Ton code est commité et pushé sur ton serveur git
Quests
Introduction to Java Generics
We already created at least one simple List for a specific Class ourself for a start although Java already provides classes for this purpose. At the time, the authors of the List classes provided by Java were written, the programmers could not know about the classes that their list will be used for. Since every class is derived from java.lang.Object, the first versions of Java simply used that class to represent the items in the List. This imposes some problems that Java generics solve.

The first versions of Java did not include generics. In fact, a similar approach used in the C++ language called templates, was explicitly rejected because it was thought to add unnecessary complexity to the Java language. Generics where introduced with Java 1.5 or short Java 5 codenamed 'Tiger' in September 2004.
🤓 By the end of this quest, you will:
✅ Understand why Generics are used ✅ Realize the limitations and challenges when using Generics
👉 Why we need generics
Take a simple list as outlined belowe
public class FixedSizeList {
private Object[] items = new Object[5];
public void add(Object object) { /* ... */ }
public Object get(final int index) { /* ... */ }
}
As you can see, the add() method of this list accepts every type of Object, since every type of Object is derived from java.lang.Object. This makes our list into a general purpose container.
Now have a look at the get() method. When you want to access an item that you put into this list, the declaration has a return type of Object. It is impossible to know what the concrete type of the returned value is. The only, least common denominator, that is clear is, that the returend value is a java.lang.Object since every class is derived from this class in Java.
To solve this problem, you could use a cast to the concrete class
// ...
Apple apple1 = new Apple();
fixedSizedList.add(apple1);
Object result = fixedSizedList.get(0);
Apple apple2 = (Apple) result;
This will work as long as you are 100% sure that the list will only ever have apples stored in the list. Remember that the add() method will accept anything so it is easy to put anything in the list.
To solve this problem, we would have to rewrite the FixedSizeList and use the Apple class for the items. Again, this will only solve the problems for Apples with a FixedSizeListForApples. For a pear, we would have to write another list and for every other class we would have to write yet another list class. This is a lot of work.
The solution to this problem is to make the type of the item more flexible by extending the language to allow parameters for the class declaration. These parameter work much like parameters for functions, but at a different level. They are used by the compiler at compile time while parameters for functions work at runtime.
To add a parameter for generics, the type parameter is added to the class in angle brackets
// ...
class GenericFixedSizeList<T> { /* ... */ }
This is comparable to methods, where the type parameter is added in parenthesis
// ...
void someMethod( Integer i) { /* ... */ }
There is a subtle difference here. The type parameter for generics has no type itself as it is clear that this is a type - or simply put, the name of a class.
Wherever the java.lang.Object type is used in the class itself, it has to be replaced with the generic type parameter name.
public class FixedSizeList<T> {
private T[] items = /* ... */
public void add(T object) { /* ... */ }
public T get(final int index) { /* ... */ }
}
To use this generic class for our Apple class, we have to provide the type parameter when we create a list.
FixedSizedList<Apple> fixedSizedList = new FixedSizedList<Apple>();
There is one important thing to keep in mind
The type parameter is resolved at compile time.
To access the items in the generic list, we need no cast anymore.
// ...
Apple apple1 = new Apple();
fixedSizedList.add(apple);
Apple apple2 = fixedSizedList.get(0);
Remember one thing like a mantra
Cast is bad
Avoid them when you can - and in most cases you can.
You can watch this video to learn more about Java Generics.
Conclusion
With this introduction to Java generics, we created a generic collection class that will save us a lot of time. Instead of writing a large number of specific classes for repetitive tasks, we can now write a single class and reuse that. There are several things to consider when using Java Generics. Look at the following Cheat Sheet for more details.
You can use your search engine to search for a cheat sheet that suits your preferences of color or that provides a more details description.
🔬 Exercise
You could use the instanceof operator to make a cast safe. Try to solve the problem for a non-generic list with three concrete types (Apple, Pear, Banana) and compare the result to the use of a generic class.
Have a look at the Composite Pattern that is related to generics and see how this could help as an alternative to solve the problem.
Will the use of a FixedSizedListOfFruit solve the problem and why or why not?
Find out why we can not write new T[5] to create the array that holds the items. You have to provide a solution to create a new instance for whatever the Generic type T will be to complete this quest.
☝️ Summary
This quest introduced Java generics, the reasone to use them and a simple implementation
💪 Challenge
The challenge for this quest is to create a generic list and write unit test for this class
Things to look for:
- Provide a test with null values
🧐 Acceptance criteria
⭙ You wrote a generic list class ⭙ You have unit tests to check if things work ⭙ Your code is commited and pushed to your git server