Entré, vu, généralisé: immergé dans Java Generics

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) { // (1) 

 sum += ((Account) account).getAmount(); // (2) 

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-type

Voici 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; // ArrayStoreException.       

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; // compile-time error.      nums.set(2, 3.14); assert ints.toString().equals("[1, 2, 3.14]"); 

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
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 sujet

Une 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); // compile-time error 

La réponse
Le 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); // compile-time error } 

La réponse
Vous 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); // Compile-time error 


<?> 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) {  // a wildcard collection  for (Object o : c) {      System.out.println(o);  } } 


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; // Ok strings = arrayList; // Unchecked assignment arrayList.add(1); //unchecked call 


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); // ! public static void reverse(List<?> list) { List<Object> tmp = new ArrayList<Object>(list); for (int i = 0; i < list.size(); i++) {   list.set(i, tmp.get(list.size()-i-1)); // compile-time error } } 

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 X

Vous 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éponse
En 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éponse
Chaque 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 {// Generic class may not extend 'java.lang.Throwable'  T t; } 



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; // (1)  ls.add("");  return ls; } 

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; // (1)  String s = stringLists[0].get(0); // (2) } 


Prenons un autre exemple:
 ArrayList<String> strings = new ArrayList<>(); ArrayList arrayList = new ArrayList(); arrayList = strings; // (1) Ok arrayList.add(1); // (2) unchecked call 

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<>(); 

ArrayListList<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()); //error: expected List<Integer>, found List<Object> 

JDK 7 compile-time error. JDK 8 . JEP-101, — . JDK 8 — :
 List.cons(42, List.<Integer>nil()); 


JEP-101 , , :
 String s = List.nil().head(); //error: expected String, found Object 

, . , 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   // Method g:()Ljava/lang/Object;        3: checkcast     #7   // class "[Ljava/lang/String;"        6: invokestatic  #8   // Method m:([Ljava/lang/String;)V        9: return     LineNumberTable:       line 15: 0       line 16: 9 


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   // Method g:()Ljava/lang/Object;        3: invokestatic  #7   // Method m:(Ljava/lang/Object;)V        6: return            LineNumberTable:       line 15: 0       line 16: 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.

Source: https://habr.com/ru/post/fr416413/


All Articles