Historique de la question.
Java a introduit les génériques dans la version 5 en utilisant l'effacement des types pour garantir la compatibilité binaire avec le code hérité. Les tableaux, cependant, sont reifiés : ils portent leur type de composant (Class) à l'exécution pour appliquer les vérifications ArrayStoreException lors de l'insertion d'éléments. Comme les paramètres de type générique comme T sont effacés à leurs limites (typiquement Object) dans le bytecode, le JVM ne peut pas résoudre T en une classe concrète à l'exécution, créant un fossé irréconciliable entre le système de types à la compilation et la vérification des tableaux à l'exécution.
Le problème.
Si le compilateur permettait new T[10], le bytecode généré instancierait Object[] alors que la variable de référence prétendait qu'il s'agissait de T[]. Cette discordance permet la pollution de la heap : un Integer pourrait être stocké dans une référence de tableau de type String[] (qui pointe en réalité vers un Object[]), contournant la protection de type du JVM. La corruption resterait latente jusqu'à ce qu'une opération de lecture ultérieure déclenche une ClassCastException loin du point d'insertion d'origine, violant la garantie de sécurité de type statique de Java et rendant le débogage incroyablement difficile.
La solution.
Les développeurs doivent éviter l'instanciation directe au profit d'alternatives sécurisées par les types. La méthode java.lang.reflect.Array.newInstance(Class<T>, int) crée un tableau avec le bon type de composant Class à l'exécution. Alternativement, utilisez Object[] avec un cast explicite lors de la récupération (en supprimant les avertissements avec @SuppressWarnings("unchecked")), ou de préférence, remplacez les tableaux par ArrayList<T> ou d'autres collections qui embrassent pleinement le système de types génériques sans nécessiter la création de tableaux à l'exécution.
Description du problème.
Lors de l'architecture d'une bibliothèque d'algèbre linéaire haute performance, l'équipe a nécessité un Matrix<T> générique pour supporter Double, Complex, et des types numériques personnalisés sans le surcoût en boxing de ArrayList<T>. Le stockage interne nécessitait un tableau bidimensionnel T[][] pour la localité de cache et la vitesse brute. Le défi résidait dans l'instanciation de T[][] dans le constructeur sans déclencher d'erreurs de compilation ou introduire des vulnérabilités subtiles de sécurité de type qui pourraient corrompre les résultats numériques.
Solution 1 : Cast non vérifié de tableau Object[].
Une proposition impliquait de caster (T[][]) new Object[rows][cols] et de supprimer l'avertissement non vérifié avec des annotations. Cette approche offrait zéro surcharge de performance et un contrôle direct de la disposition mémoire. Cependant, elle créait un contrat fragile : si la Matrix exposait son tableau interne via un getter, le code externe pourrait polluer la heap en insérant des types incompatibles, menant à des échecs ClassCastException lors de la multiplication de matrices qui seraient presque impossibles à retracer au point de corruption d'origine.
Solution 2 : Casting par élément avec stockage Object.
Une autre option stockait des données sous forme de Object[][] et castait des éléments individuels en T à chaque opération de lecture. Cela garantissait une détection immédiate des discordances de type au site de récupération, simplifiant considérablement le débogage. L'inconvénient était un code d'ossature substantiel et une pénalité de performance mesurable de 5 à 10 % dans des boucles de calcul serrées en raison des instructions de bytecode checkcast répétées, ce qui contournait l'objectif principal de la bibliothèque d'égaler les performances des tableaux natifs.
Solution 3 : Réflexion via Array.newInstance().
L'équipe a finalement utilisé Array.newInstance(componentType, rows, cols), exigeant que les appelants fournissent un jeton Class<T>. Cela a généré un tableau avec le type d'exécution précis, prévenant complètement la pollution de la heap tout en maintenant la vitesse brute des tableaux natifs. Le coût unique de l'instantiation réflexive lors de la création de matrices était négligeable comparé à la charge de travail computationnelle O(n³) des opérations matricielles, et la solution offrait une sécurité de type à la compilation sans casts non sécurisés ni surcharge par accès.
Résultat.
La bibliothèque a été expédiée sans aucune erreur ArrayStoreException ou ClassCastException signalée pendant trois ans d'utilisation intensive dans des applications de finance quantitative. L'approche réflexive a permis un support sans couture tant pour les wrappers primitifs que pour les types personnalisés complexes, tandis que la vérification stricte des types a empêché la corruption silencieuse des données dans des calculs financiers critiques. Les benchmarks de performance ont confirmé que la surcharge réflexive unique restait négligeable par rapport au coût computationnel des opérations matricielles.
**Pourquoi le tableau de joker List<?>[]** évite-t-il les pièges de sécurité de type qui touchent **List<String>[]**, bien que les deux soient des tableaux de types paramétrés ?** **List<?>[] représente un tableau de listes génériques inconnues, que le compilateur traite comme un tableau de type brut avec la restriction critique que vous ne pouvez ajouter aucun élément non nul (car il ne peut pas vérifier la compatibilité des types). List<String>[] impliquerait un tableau où chaque élément est garanti d'être un List<String>, mais après effacement, le JVM ne voit que List[]. Si cela était permis, vous pourriez attribuer un List<Integer> à un élément du tableau (puisque à l'exécution, il n'est que List), puis le récupérer comme List<String> et rencontrer une ClassCastException en accédant aux éléments. La variante joker empêche cela en interdisant complètement les écritures, préservant la sécurité de type grâce aux contraintes d'immuabilité.
Comment l'invocation de méthode varargs instancie-t-elle silencieusement un tableau générique au site d'appel, et pourquoi @SafeVarargs masque plutôt que résout le risque de pollution de la heap ?
Lors de la déclaration void process(T... items), le compilateur synthétise un tableau T[] pour contenir les arguments, qui devient en réalité un Object[] après effacement. L'annotation @SafeVarargs supprime l'avertissement du compilateur mais ne modifie pas le bytecode ; la méthode reçoit toujours un Object[] qui se fait passer pour un T[]. Le danger persiste : si la méthode stocke le tableau items dans un champ ou permet qu'il s'échappe, et que ce tableau contient des éléments non T (possible à travers la pollution de la heap depuis le site d'appel), des lectures ultérieures déclenchent une ClassCastException. La véritable sécurité nécessite de copier défensivement items dans un ArrayList<T> ou d'utiliser Array.newInstance à l'intérieur du corps de la méthode.
Lorsque vous utilisez Arrays.copyOf ou System.arraycopy avec des tableaux génériques, pourquoi un ClassCastException pourrait-il survenir même lorsque la source et la destination semblent compatibles en termes de type, et comment Class.getComponentType() fournit-elle une solution ?
Arrays.copyOf utilise en interne Array.newInstance avec la classe d'exécution de l'array original. Si vous possédez un T[] qui a été créé via un cast non sécurisé à partir de Object[], son type de composant est Object, et non T. Lors de la copie via Arrays.copyOf(original, newLength), vous recevez un Object[] qui ne peut pas être casté en T[], lançant immédiatement un ClassCastException. La solution implique de suivre le jeton Class<T> séparément et d'invoquer Array.newInstance(componentType, length) plutôt que de s'appuyer sur l'objet de classe du tableau lui-même, garantissant que le nouveau tableau correspond au type générique prévu plutôt qu'à son implémentation effacée.