par , 13/11/2015 à 12h34 (1113 Affichages)
Auteur : Gokan EKINCI
Date de première publication : 2015-11-13
Licence : CC BY-NC-SA
Aujourd'hui je vais vous présenter quelques limitations des generics en Java, pour cela je vais vous montrer quelques exemples qu'on ne peut pas réaliser en Java mais qui auraient pu fonctionner ailleurs (en C++ et C#).
La généricité en Java permet de faire à peu près la même chose que les « templates » en C++ ou les « generics » en C#, mais contrairement au C++ qui est compilé (vos types sont remplacés par les vrais types lors de la compilation, donc augmentation du nombre de ligne de code généré en théorie mais surtout augmentation du poids du binaire), ou en C# où les types sont déterminés à l’exécution (JIT), en Java nous avons des opérations de cast lors de la compilation.
Java et C# sont 2 langages qui se ressemblent en bien des points mais la généricité en Java est basé sur le « type erasure », tandis qu’en C# sur la « reification ».
Il y a bien des différences entre ces 2 mécanismes, je ne vous cacherais pas que celui de Java possède des limitations… Pourquoi ? Pour des raisons de son apparition tardive dans le langage (Java 5), et la volonté de garder compatibilité avec les API déjà existante.
Contrairement aux autres langages, en Java il n’est pas possible :
D'utiliser un type primitif (int, long, double etc) :
1 2
| List<int> list1 = new ArrayList<int>(); // Interdit
List<Integer> list2 = new ArrayList<Integer>(); // OK |
Pourquoi ? Réponse courte : Parce que les types primitifs ne sont pas castable avec Object.
D'instancier une classe générique :
1 2 3 4 5 6 7 8 9 10
| public class MyClass1<T1>{
T1 gen1; // OK
MyClass1<T1> gen2; // OK
MyClass2<Integer>[] gen3; // OK
// Interdit en Java, mais possible en C++ (avec les templates), ou C# avec la contrainte new()
T1 gen4 = new T1();
T1[] gen5 = new T1[10];
MyClass2<Integer>[] gen6 = new MyClass2<Integer>[10];
} |
Il existe cependant une "astuce" pour rendre gen6 générique :
MyClass2<Integer>[] gen6 = ( MyClass2<Integer>[] ) new MyClass2[10];
De surcharger une méthode générique :
1 2 3 4
| public void method(List<Foo> liste) { }
// Tentative de surcharge : interdit en Java, possible en C++ et C#
public void method(List<Bar> liste){ } |
De créer une classe générique qui hérite d'Exception :
1 2 3 4 5
| // Java
class MyException<T> extends Exception{ } // Interdit
// C#
class MyException<T> : Exception{ } // OK |
D’utiliser l’opérateur instanceof avec le type générique « T » (même si T est covariant) :
1 2 3 4 5
| // Java
if(myObject instanceof T){ } // Erreur
// C#
if(myObject is T){ } // OK |
D’utiliser le mot-clé class (réfléxion) avec les types génériques :
1 2 3 4 5 6
|
// Java
Class<T> c = T.class; // Interdit
// C#
Type type = typeof(T); // OK |
Astuce Java : Utilisez Class<T> comme paramètre de méthode à chaque fois que vous avez besoin d'utiliser T.class à l'intérieur d'une méthode, pas pratique mais efficace.
Ma conclusion est qu'il est possible de rencontrer des cas où on peut avoir besoin des exemples que j'ai montré en Java, cependant il existe toujours (ou très souvent) un moyen de trouver une alternative, comme utiliser Class<T> ou instancier un objet puis le faire passer en paramètre d'une méthode.
Et vous, qu'en pensez-vous ?