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
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é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.