Java Generics est l'un des changements les plus importants de l'histoire du langage Java. Les génériques disponibles avec Java 5 ont rendu l'utilisation de Java Collection Framework plus facile, plus pratique et plus sûre. Les erreurs associées à une mauvaise utilisation des types sont désormais détectées au stade de la compilation. Oui, et le langage Java lui-même est devenu encore plus sûr. Malgré l'apparente simplicité des types génériques, de nombreux développeurs ont du mal à les utiliser. Dans cet article, je parlerai des fonctionnalités de travail avec Java Generics, afin que vous ayez moins de ces difficultés. Utile si vous n'êtes pas un gourou générique, et vous aidera à éviter beaucoup de difficultés lors de l'immersion dans le sujet.

Travailler avec des collections
Supposons qu'une banque ait besoin de calculer le montant des économies dans les comptes clients. Avant l'avènement des «génériques», la méthode de calcul de la somme ressemblait à ceci:
public long getSum(List accounts) {   long sum = 0;   for (int i = 0, n = accounts.size(); i < n; i++) {       Object account = accounts.get(i);       if (account instanceof Account) {           sum += ((Account) account).getAmount();       }   }   return sum; } 
Nous avons itéré, parcouru la liste des comptes et vérifié si l'élément de cette liste est vraiment une instance de la classe 
Account , c'est-à-dire le compte de l'utilisateur. Le type de notre objet de la classe 
Account et la méthode 
getAmount ont été 
getAmount , ce qui a renvoyé le montant de ce compte. Ensuite, ils ont résumé le tout et retourné le montant total. Deux étapes ont été nécessaires:
 if (account instanceof Account) {  
 sum += ((Account) account).getAmount();  
Si vous ne vérifiez pas ( 
instanceof ) l'appartenance à la classe 
Account , alors à la deuxième étape une 
ClassCastException est possible - c'est-à-dire un plantage du programme. Par conséquent, une telle vérification était obligatoire.
Avec l'avènement des génériques, le besoin de vérification de type et de transtypage a disparu:
 public long getSum2(List<Account> accounts) {  long sum = 0;  for (Account account : accounts) {      sum += account.getAmount();  }  return sum; } 
Méthode Now
 getSum2(List<Account> accounts) 
accepte comme arguments uniquement une liste d'objets de la classe 
Account . Cette restriction est indiquée dans la méthode elle-même, dans sa signature, le programmeur ne peut tout simplement pas transférer d'autre liste - uniquement la liste des comptes clients.
Nous n'avons pas besoin de vérifier le type d'éléments de cette liste: cela est impliqué par la description du type du paramètre de méthode
 List<Account> accounts 
(peut être lu comme une 
Account ). Et le compilateur générera une erreur si quelque chose ne va pas - c'est-à-dire, si quelqu'un essaie de passer une liste d'objets autres que la classe 
Account à cette méthode.
Dans la deuxième ligne du chèque, le besoin a également disparu. Si nécessaire, le 
casting sera fait lors de la compilation.
Principe de substitution
Le principe de substitution de Barbara Liskov est une définition spécifique d'un sous-type dans la programmation orientée objet. L'idée de Liskov d'un «sous-type» définit le concept de substitution: si 
S est un sous-type de 
T , alors les objets de type 
T dans un programme peuvent être remplacés par des objets de type 
S sans aucune modification des propriétés souhaitées de ce programme.
| Tapez 
 | Sous-type 
 | 
| Numéro 
 | Entier 
 | 
| Liste <E> 
 | ArrayList <E> 
 | 
| Collection <E> 
 | Liste <E> 
 | 
| Iterable <E> 
 | Collection <E> 
 | 
Exemples de relations de type / sous-typeVoici un exemple d'utilisation du principe de substitution en Java:
 Number n = Integer.valueOf(42); List<Number> aList = new ArrayList<>(); Collection<Number> aCollection = aList; Iterable<Number> iterable = aCollection; 
Integer est un sous-type de 
Number , par conséquent, la variable 
n type 
Number peut recevoir la valeur 
Integer.valueOf(42) la 
Integer.valueOf(42) .
Covariance, contravariance et invariance
Tout d'abord, un peu de théorie. La covariance est la préservation de la hiérarchie d'héritage des types source dans les types dérivés dans le même ordre. Par exemple, si le 
chat est un sous-type d' 
animaux , alors l' 
ensemble de <chats> est un sous-type de l' 
ensemble de <animaux> . Par conséquent, compte tenu du principe de substitution, on peut effectuer la mission suivante:
Many <Animals> = Many <Cats>La contravariance est l'inversion de la hiérarchie des types de source dans les types dérivés. Par exemple, si le 
chat est un sous-type des 
, l' 
ensemble <Animaux> est un sous-type de l' 
ensemble <Cats> . Par conséquent, compte tenu du principe de substitution, on peut effectuer la mission suivante:
Many <Cats> = Many <Animals>Invariance - manque d'héritage entre les types dérivés. Si le 
chat est un sous-type d' 
animaux , alors l' 
ensemble de <chats> n'est pas un sous-type de l' 
ensemble de <animaux> et l' 
ensemble de <animaux> n'est pas un sous-type de l' 
ensemble de <chats> .
Les tableaux en Java sont covariants . Le type 
S[] est un sous-type de 
T[] si 
S est un sous-type de 
T Exemple d'affectation:
 String[] strings = new String[] {"a", "b", "c"}; Object[] arr = strings; 
Nous avons attribué un lien vers un tableau de chaînes à la variable 
arr , dont le type est 
« » . Si les tableaux n'étaient pas covariants, nous ne serions pas en mesure de le faire. Java vous permet de le faire, le programme se compile et s'exécute sans erreur.
 arr[0] = 42;  
Mais si nous essayons de changer le contenu du tableau via la variable 
arr et d'y écrire le nombre 42, nous obtiendrons une 
ArrayStoreException au stade de l'exécution du programme, car 42 n'est pas une chaîne, mais un nombre. C'est l'inconvénient de la covariance des tableaux Java: nous ne pouvons pas effectuer de vérifications au stade de la compilation, et quelque chose peut déjà se casser lors de l'exécution.
Les "génériques" sont invariants. Voici un exemple:
 List<Integer> ints = Arrays.asList(1,2,3); List<Number> nums = ints;  
Si vous prenez une liste d'entiers, ce ne sera pas un sous-type de type 
Number , ni aucun autre sous-type. Il n'est qu'un sous-type de lui-même. Autrement dit, 
List <Integer> est un 
List<Integer> et rien d'autre. Le compilateur s'assurera que la variable 
ints déclarée comme une liste d'objets de la classe 
Integer ne contient que des objets de la classe 
Integer et rien d'autre. Au stade de la compilation, une vérification est effectuée et rien ne tombera dans notre runtime.
Caractères génériques
Les génériques sont-ils toujours invariants? Non. Je vais donner des exemples:
 List<Integer> ints = new ArrayList<Integer>(); List<? extends Number> nums = ints; 
C'est la covariance. 
List<Integer> - sous-type de 
List<? extends Number> List<? extends Number> List<Number> nums = new ArrayList<Number>(); List<? super Integer> ints = nums; 
C'est de la contravariance. 
List<Number> est un sous-type de 
List<? super Integer> List<? super Integer> .
Un enregistrement comme 
"? extends ..." ou 
"? super ..." est appelé un caractère générique ou un caractère générique, avec une limite supérieure ( 
extends ) ou une limite inférieure ( 
super ). 
List<? extends Number> List<? extends Number> peut contenir des objets dont la classe est 
Number ou hérite de 
Number . 
List<? super Number> List<? super Number> peut contenir des objets dont la classe est 
Number ou dont 
Number est un héritier (supertype from 
Number ).
|  
 | étend B - caractère générique avec limite supérieure super B - caractère générique avec une borne inférieure
 où B - représente la frontière
 
 Un enregistrement de la forme T 2 <= T 1 signifie que l'ensemble des types décrits par T 2 est un sous-ensemble de l'ensemble des types décrits par T 1
 
 c'est-à-dire
 Numéro <=? étend l'objet
 ? étend Number <=? étend l'objet
 et
 ? super objet <=? super numéro
 
 
 | 
Plus d'interprétation mathématique du sujetUne paire de tâches pour tester les connaissances:
1. Pourquoi l'erreur de compilation dans l'exemple ci-dessous? Quelle valeur puis-je ajouter à la liste des 
nums ?
 List<Integer> ints = new ArrayList<Integer>(); ints.add(1); ints.add(2); List<? extends Number> nums = ints; nums.add(3.14);  
La réponseLe conteneur doit-il être déclaré avec un caractère générique ? extends ? extends , vous ne pouvez lire que les valeurs. Rien ne peut être ajouté à la liste sauf null . Pour ajouter un objet à la liste, nous avons besoin d'un autre type de caractère générique - ? super ? super
 2. Pourquoi ne puis-je pas obtenir un article de la liste ci-dessous?
 public static <T> T getFirst(List<? super T> list) {  return list.get(0);  
La réponseVous ne pouvez pas lire un élément d'un conteneur avec un caractère générique 
? super ? super , sauf pour un objet de classe 
Object public static <T> Object getFirst(List<? super T> list) {  return list.get(0); } 
 Le principe Get and Put ou PECS (le producteur étend Consumer Super)
La fonctionnalité générique avec des limites supérieures et inférieures offre des fonctionnalités supplémentaires liées à l'utilisation sûre des types. Vous ne pouvez lire qu'à partir d'un type de variable, seulement écrire à un autre (l'exception est la possibilité d'écrire 
null pour 
extends et de lire 
Object pour 
super ). Pour qu'il soit plus facile de se rappeler quand utiliser quel caractère générique, il existe le principe PECS - Producer Extends Consumer Super.
- Si nous avons déclaré un caractère générique avec extend , alors c'est producteur . Il "produit" seulement, fournit un élément du contenant et n'accepte rien.
 
- Si nous avons annoncé un caractère générique avec super , alors c'est le consommateur . Il accepte seulement, mais ne peut rien fournir.
 
Envisagez d'utiliser Wildcard et le principe PECS en utilisant la méthode de copie dans la classe java.util.Collections comme exemple.
 public static <T> void copy(List<? super T> dest, List<? extends T> src) { … } 
La méthode copie les éléments de la liste 
src origine dans la liste 
dest . 
src - déclaré avec caractère générique 
? extends ? extends et est le producteur, et 
dest est déclaré avec un caractère générique 
? super ? super et est un consommateur. Étant donné la covariance et la contravariance des caractères génériques, vous pouvez copier des éléments de la liste des 
nums vers la liste des 
nums :
 List<Number> nums = Arrays.<Number>asList(4.1F, 0.2F); List<Integer> ints = Arrays.asList(1,2); Collections.copy(nums, ints); 
Si nous confondons les paramètres de la méthode de copie par erreur et essayons de copier de la liste 
nums vers la liste 
ints , le compilateur ne nous permettra pas de faire ceci:
 Collections.copy(ints, nums);  
<?> et types Raw
Vous trouverez ci-dessous un caractère générique avec un caractère générique illimité. Nous venons de mettre 
<?> , Sans les mots 
extends clés 
super ou 
extends :
 static void printCollection(Collection<?> c) {   
En fait, un tel caractère générique "illimité" est toujours limité, vu d'en haut. 
Collection<?> Est également un caractère générique, comme " 
? extends Object ". Un enregistrement du formulaire 
Collection<?> Équivaut à 
Collection<? extends Object> Collection<? extends Object> , ce qui signifie que la collection peut contenir des objets de n'importe quelle classe, car toutes les classes en Java héritent d' 
Object - donc la substitution est appelée illimitée.
Si nous omettons l'indication de type, par exemple, comme ici:
 ArrayList arrayList = new ArrayList(); 
puis ils disent que 
ArrayList est le type 
Raw de la 
ArrayList paramétrée 
<T> . En utilisant les types Raw, nous retournons à l'ère des génériques et abandonnons consciemment toutes les fonctionnalités inhérentes aux types paramétrés.
Si nous essayons d'appeler une méthode paramétrée sur le type Raw, le compilateur nous donnera un avertissement «Appel non vérifié». Si nous essayons d'assigner une référence à un type Raw paramétré à un type, le compilateur donnera un avertissement «Affectation non vérifiée». Ignorer ces avertissements, comme nous le verrons plus loin, peut entraîner des erreurs lors de l'exécution de notre application.
 ArrayList<String> strings = new ArrayList<>(); ArrayList arrayList = new ArrayList(); arrayList = strings;  
Capture de caractères génériques
Essayons maintenant d'implémenter une méthode qui permute les éléments d'une liste dans l'ordre inverse.
 public static void reverse(List<?> list);  
Une erreur de compilation s'est produite car la méthode 
reverse prend une liste avec un caractère générique illimité 
<?> Comme argument.
<?> signifie la même chose que 
<? extends Object> <? extends Object> . Par conséquent, selon le principe PECS, la 
list est 
producer . Et le 
producer ne produit que des éléments. Et nous dans la boucle 
for appelons la méthode 
set() , c'est-à-dire essayer d'écrire dans la 
list . Et nous nous reposons donc sur la protection Java, qui ne nous permet pas de définir une valeur par index.
Que faire Le modèle de 
Wildcard Capture nous aidera. Ici, nous créons une méthode générique 
rev . Il est déclaré avec une variable de type 
T Cette méthode accepte une liste de types 
T , et nous pouvons faire un ensemble.
 public static void reverse(List<?> list) { rev(list); } private static <T> void rev(List<T> list) { List<T> tmp = new ArrayList<T>(list); for (int i = 0; i < list.size(); i++) {   list.set(i, tmp.get(list.size()-i-1)); } } 
Maintenant, tout va compiler avec nous. La capture générique a été capturée ici. Lorsque la méthode 
reverse(List<?> list) est appelée 
reverse(List<?> list) , une liste de certains objets (par exemple, des chaînes ou des entiers) est passée en argument. Si nous pouvons capturer le type de ces objets et l'affecter à une variable de type 
X , alors nous pouvons conclure que 
T est 
XVous pouvez en savoir plus sur 
Wildcard Capture ici et 
ici .
Conclusion
Si vous devez lire à partir du conteneur, utilisez un caractère générique avec la bordure supérieure " 
? extends ". Si vous devez écrire dans le conteneur, utilisez un caractère générique avec une bordure inférieure de " 
? super ". N'utilisez pas de caractères génériques si vous devez enregistrer et lire.
N'utilisez pas de types 
Raw ! Si l'argument type n'est pas défini, utilisez le caractère générique 
<?> .
Variables de type
Lorsque nous écrivons l'identifiant entre crochets, par exemple, 
<T> ou 
<E> lors de la déclaration d'une classe ou d'une méthode, nous créons 
une variable de type . Une variable de type est un identifiant non qualifié qui peut être utilisé comme type dans le corps d'une classe ou d'une méthode. Une variable de type peut être délimitée ci-dessus.
 public static <T extends Comparable<T>> T max(Collection<T> coll) { T candidate = coll.iterator().next(); for (T elt : coll) {   if (candidate.compareTo(elt) < 0) candidate = elt; } return candidate; } 
Dans cet exemple, l'expression 
T extends Comparable<T> définit 
T (une variable de type) délimitée ci-dessus par le type 
Comparable<T> . Contrairement aux caractères génériques, les variables de type ne peuvent être limitées qu'en haut ( 
extends uniquement). Impossible d'écrire 
super . De plus, dans cet exemple, 
T dépend de lui-même, il est appelé 
recursive bound - une frontière récursive.
Voici un autre exemple de la classe Enum:
 public abstract class Enum<E extends Enum<E>>implements Comparable<E>, Serializable 
Ici, la classe Enum est paramétrée par le type E, qui est un sous-type d' 
Enum<E> .
Limites multiples
Multiple Bounds multiples - contraintes multiples. Il est écrit par le caractère " 
& ", c'est-à-dire que nous disons que le type représenté par une variable de type 
T doit être limité par le haut par la classe 
Object et l'interface 
Comparable .
 <T extends Object & Comparable<? super T>> T max(Collection<? extends T> coll) 
Object & Comparable<? super T> enregistrement 
Object & Comparable<? super T> Object & Comparable<? super T> forme le type d'intersection 
Multiple Bounds . La première limitation - dans ce cas, 
Object - est utilisée pour l' 
erasure , le processus de remplacement des types. Il est exécuté par le compilateur au stade de la compilation.
Conclusion
Une variable de type ne peut être limitée qu'au-dessus d'un ou de plusieurs types. Dans le cas de contraintes multiples, la bordure gauche (la première contrainte) est utilisée dans le processus d'écrasement (Type Erasure).
Effacement du type
L'effacement de type est un mappage de types (incluant éventuellement des types paramétrés et des variables de type) avec des types qui ne sont jamais des types paramétrés ou des types de variables. Nous écrivons le brassage de type 
T comme 
|T| .
L'affichage de la purée est défini comme suit:
- Écraser le type paramétré G < T1 , ..., Tn > est | G |
- Écraser un TC de type imbriqué est | T |. C
- Écraser le type de tableau T [] est | T | []
- Écraser une variable de type, c'est écraser sa bordure gauche
- Écraser tout autre type est ce type lui-même
Pendant l'exécution de Type Erasure (type mashing), le compilateur effectue les actions suivantes:
- ajoute la coulée de caractères pour assurer la sécurité des caractères si nécessaire
- génère des méthodes Bridge pour maintenir le polymorphisme
| T (Type) 
 | | T | (Type de purée) 
 | 
| Liste <entier>, liste <chaîne>, liste <liste <chaîne >> 
 | Liste 
 | 
| Liste <entier> [] 
 | Liste [] 
 | 
| Liste 
 | Liste 
 | 
| int 
 | int 
 | 
| Entier 
 | Entier 
 | 
| <T étend Comparable <T>> 
 | Comparable 
 | 
| <T étend Objet & Comparable <? super T >> 
 | Objet 
 | 
| LinkedCollection <E> .Node 
 | LinkedCollection.Node 
 | 
Ce tableau montre à quoi les différents types se transforment pendant le processus de brassage, Type Erasure.
Dans la capture d'écran ci-dessous sont deux exemples du programme:

La différence entre les deux est qu'une erreur de compilation se produit à gauche et à droite, tout se compile sans erreur. Pourquoi?
La réponseEn Java, deux méthodes différentes ne peuvent pas avoir la même signature. Dans le processus d'effacement de type, le compilateur ajoutera la méthode bridge 
public int compareTo(Object o) . Mais la classe contient déjà une méthode avec une telle signature qu'elle provoquera une erreur lors de la compilation.
Compilez la classe Name en supprimant la 
compareTo(Object o) et examinez le bytecode résultant à l'aide de javap:
 # javap Name.class Compiled from "Name.java" public class ru.sberbank.training.generics.Name implements java.lang.Comparable<ru.sberbank.training.generics.Name> { public ru.sberbank.training.generics.Name(java.lang.String); public java.lang.String toString(); public int compareTo(ru.sberbank.training.generics.Name); public int compareTo(java.lang.Object); } 
Nous voyons que la classe contient une méthode 
int compareTo(java.lang.Object) , bien que nous l'avons supprimée du code source. Il s'agit de la méthode de pontage ajoutée par le compilateur.
 Types réifiables
En Java, nous disons qu'un type est 
reifiable si ses informations sont entièrement accessibles au moment de l'exécution. Les types réifiables incluent:
- Types primitifs ( int , long , booléen )
- Types non paramétrés (non génériques) ( chaîne , entier )
- Types paramétrés dont les paramètres sont représentés comme des caractères génériques non limités (caractères génériques illimités) ( Liste <?> , Collection <?> )
- Types bruts (non formés) ( List , ArrayList )
- Tableaux dont les composants sont des types réifiables ( int [] , Number [] , List <?> [] , List [ )
Pourquoi des informations sur certains types sont-elles disponibles mais pas sur d'autres? Le fait est qu'en raison du processus d'écrasement des types par le compilateur, des informations sur certains types peuvent être perdues. S'il est perdu, ce type ne sera plus réifiable. Autrement dit, il n'est pas disponible au moment de l'exécution. Si disponibles - respectivement, réifiables.
La décision de ne pas rendre tous les types génériques disponibles au moment de l'exécution est l'une des décisions de conception les plus importantes et les plus conflictuelles du système de types Java. Ceci est fait, tout d'abord, pour la compatibilité avec le code existant. J'ai dû payer pour la compatibilité de la migration - l'accessibilité complète d'un système de types génériques au moment de l'exécution n'est pas possible.
Quels types ne sont pas réifiables:
- Variable de type ( T )
- Type paramétré avec le type de paramètre spécifié ( List <Number> ArrayList <String> , List <List <String>> )
- Un type paramétré avec la limite supérieure ou inférieure spécifiée ( List <? Extends Number>, Comparable <? Super String> ). Mais voici une réserve: Liste <? étend Object> - non réifiable, mais List <?> - réifiable
Et encore une tâche. Pourquoi dans l'exemple ci-dessous ne peut pas créer une exception paramétrée?
 class MyException<T> extends Exception {  T t; } 
La réponseChaque expression catch dans try-catch vérifie le type de l'exception reçue pendant l'exécution du programme (ce qui équivaut à instanceof), respectivement, le type doit être réifiable. Par conséquent, Throwable et ses sous-types ne peuvent pas être paramétrés.
 class MyException<T> extends Exception { 
 Avertissements non vérifiés
La compilation de notre application peut produire ce que l'on appelle un avertissement 
Unchecked Warning - un avertissement indiquant que le compilateur n'a pas pu déterminer correctement le niveau de sécurité d'utilisation de nos types. Ce n'est pas une erreur, mais un avertissement, vous pouvez donc l'ignorer. Mais il est conseillé de tout réparer afin d'éviter des problèmes à l'avenir.
Pollution en tas
Comme nous l'avons mentionné précédemment, l'attribution d'une référence à un type Raw à une variable d'un type paramétré conduit à l'avertissement «Affectation non vérifiée». Si nous l'ignorons, une situation appelée « 
Heap Pollution » (pollution en tas) est possible. Voici un exemple:
 static List<String> t() {  List l = new ArrayList<Number>();  l.add(1);  List<String> ls = l;  
Sur la ligne (1), le compilateur avertit de "Affectation non vérifiée".
Nous devons donner un autre exemple de «pollution en tas» - lorsque nous utilisons des objets paramétrés. L'extrait de code ci-dessous montre clairement qu'il n'est pas autorisé d'utiliser des types paramétrés comme arguments d'une méthode utilisant 
Varargs . Dans ce cas, le paramètre de méthode m est 
List<String>… , i.e. en fait, un tableau d'éléments de type 
List<String> . Étant donné la règle d'affichage des types lors du 
stringLists , le type 
stringLists se transforme en un tableau de listes brutes ( 
List[] ), c'est-à-dire l'affectation peut être effectuée 
Object[] array = stringLists; puis écrivez dans un 
array un objet autre que la liste des chaînes (1), qui 
ClassCastException dans la chaîne (2).
 static void m(List<String>... stringLists) {  Object[] array = stringLists;  List<Integer> tmpList = Arrays.asList(42);  array[0] = tmpList;  
Prenons un autre exemple:
 ArrayList<String> strings = new ArrayList<>(); ArrayList arrayList = new ArrayList(); arrayList = strings;  
Java permet l'affectation en ligne (1). Cela est nécessaire pour la compatibilité descendante. Mais si nous essayons d'exécuter la méthode 
add dans la ligne (2), nous obtenons un avertissement d' 
Unchecked call - le compilateur nous avertit d'une erreur possible. En fait, nous essayons d'ajouter un entier à la liste des chaînes.
La réflexion
Bien que, lors de la compilation, les types paramétrés subissent une procédure d'effacement de type, nous pouvons obtenir des informations à l'aide de Reflection.
- Tous réifiables sont disponibles via le mécanisme de réflexion.
 
- Des informations sur le type de champs de classe, les paramètres de méthode et les valeurs renvoyées par ceux-ci sont disponibles via Reflection.
 
Reflection 
Reifiable , . , , , - , :
 java.lang.reflect.Method.getGenericReturnType() 
Generics 
java.lang.Class . :
 List<Integer> ints = new ArrayList<Integer>(); Class<? extends List> k = ints.getClass(); assert k == ArrayList.class; 
ints List<Integer> ArrayList< Integer> . 
ints.getClass() Class<ArrayLis> , 
List<Integer> List . 
Class<ArrayList> k Class<? extends List> , ? 
extends . 
ArrayList.class Class<ArrayList> .
Conclusion
, Reifiable. Reifiable : , , , Raw , reifiable.
Unchecked Warnings « » .
Reflection , Reifiable. Reflection , .
Type Inference
« ». () . :
 List<Integer> list = new ArrayList<Integer>(); 
- Java 7 
ArrayList :
 List<Integer> list = new ArrayList<>(); 
ArrayList – 
List<Integer> . 
type inference .
Java 8 JEP 101.
Type Inference. :
- (reduction)
 
- (incorporation)
 
- (resolution)
 
: , , — .
, . JEP 101 .
, :
 class List<E> {  static <Z> List<Z> nil() { ... };  static <Z> List<Z> cons(Z head, List<Z> tail) { ... };  E head() { ... } } 
List.nil() :
 List<String> ls = List.nil(); 
, 
List.nil() String — JDK 7, .
, , , :
 List.cons(42, List.nil());  
JDK 7 compile-time error. JDK 8 . JEP-101, — . JDK 8 — :
 List.cons(42, List.<Integer>nil()); 
JEP-101 , , :
 String s = List.nil().head();  
, . , JDK , :
 String s = List.<String>nil().head(); 
JEP 101 StackOverflow . , , 7- , 8- – ? :
 class Test {  static void m(Object o) {      System.out.println("one");  }  static void m(String[] o) {      System.out.println("two");  }  static <T> T g() {      return null;  }  public static void main(String[] args) {      m(g());  } } 
- JDK1.8:
   public static void main(java.lang.String[]);   descriptor: ([Ljava/lang/String;)V   flags: ACC_PUBLIC, ACC_STATIC   Code:     stack=1, locals=1, args_size=1        0: invokestatic  #6    
0 
g:()Ljava/lang/Object; java.lang.Object . , 3 («») , 
java.lang.String , 6 
m:([Ljava/lang/String;) , «two».
- JDK1.7 – Java 7:
   public static void main(java.lang.String[]);   flags: ACC_PUBLIC, ACC_STATIC   Code:     stack=1, locals=1, args_size=1        0: invokestatic  #6    
, 
checkcast , Java 8, 
m:(Ljava/lang/Object;) , «one». 
Checkcast – , Java 8.
, Oracle 
JDK1.7 JDK 1.8 , Java, , .
, Java 8 , Java 7, :
 public static void main(String[] args) { m((Object)g()); } 
Conclusion
Java Generics . , :
- Bloch, Joshua. Effective Java. Third Edition. Addison-Wesley. ISBN-13: 978-0-13-468599-1
 
, Java Generics.