3. Chapitre 1
Introduction
Ce rapport est un r´esum´e de l’article intitul´e ”Refactoring Java Generics by Inferring
Wildcards, In Practice” ´ecrit par John Altidor et Yannis Smaragdakis. Cet article traite
d’un outil, un algorithme, permettant de renommer et inf´erer des types plus g´en´eriques
d’instances de g´en´eriques Java en utilisant des Wildcards. Des statistiques ont montr´e que
sur les six principales librairies Java utilisant les g´en´eriques, 34% des d´eclarations valables
de signatures de type variant peuvent ˆetre g´en´eralis´ees, c’est `a dire avec des types wildcard
plus g´en´eraux. Or, pour une g´en´eralisation, il faut en moyenne mettre `a jour 146 autres
d´eclarations. Cela montre qu’il est tr`es fastidieux de le faire `a la main .
C’est dans cette perspective que nous allons ´etudier les principes de cet outil et voir
quelques exemples o`u celui ci pourrait am´eliorer notre code. Nous ´etudierons dans un
second temps sa s´emantique, ainsi que les principes de g´en´eralisations avec ses probl`emes
et les solutions apport´ees. Nous analyserons ensuite son algorithme pour enfin terminer
par ses applications.
4. Chapitre 2
D´efinition de la variance
2.1 Pr´emisse
La maintenance, la s´ecurit´e et la fiabilit´e des programmes Java augmentent quand les
librairies sont r´enomm´ees pour d´efinir des classes g´en´eriques. En effet, les g´en´eriques per-
mettent `a l’utilisateur d’indiquer au compilateur le type des ´el´ements dans une collection
et donc d’augmenter la s´ecurit´e en ´eliminant les cast douteux. Seulement, les g´en´eriques
restreignent le sous typage comme nous le verrons dans un exemple ci-dessous.
Le m´ecanisme de variance dans les langages de programmation modernes essaie de r´esoudre
le probl`eme en autorisant deux instanciations d’un g´en´erique qui est sous type d’un autre.
Le syst`eme de typage java utilise lui, le concept de ”wildcard”. C’est `a dire, lors de l’uti-
lisation d’une classe, nous pouvons choisir de sp´ecifier si elle r´ef`ere `a une ”covariante”
”contravariante” ou ”invariante” version de la classe.
2.2 Variance
Afin de mieux comprendre les principes de l’outil, il est n´ecessaire de comprendre la
notion de variance. Prenons un exemple, en regardant la diff´erence entre les tableaux et
les listes Java. Commen¸cons par les tableaux :
Number [ ] nombre = new Number [ 3 ] ;
nombre [ 0 ] = new Integer (10) ;
nombre [ 1 ] = new Double (3.14) ;
nombre [ 2 ] = new Byte (0) ;
On peut voir ici qu’un tableau peut contenir des ´el´ements de type T et de n’importe
quel sous type de celui-ci. Java fait ´egalement ´etat qu’un tableau S[] est un sous type de
T[] si S est sous type de T. On peut alors ´ecrire :
Integer [ ] e n t i e r s = {1 ,2 ,3 ,4};
Number [ ] nombre = e n t i e r s ; // correct car Integer est sous type de Number
En revanche, avec les listes, cela soul`eve des probl`emes :
5. 2.2 Variance 4
List<Integer > e n t i e r s = new ArrayList<Integer >() ;
e n t i e r s . add (1) ;
e n t i e r s . add (2) ;
List<Number> nombres = e n t i e r s ; // erreur de compilation
Il y a un probl`eme avec les types g´en´eriques. Le compilateur nous interdit formellement
de faire ¸ca. Tout ceci affecte le pouvoir du polymorphisme en java. La solution est d’utiliser
les outils des g´en´eriques java : la covariance et la contravariance.
2.2.1 La covariance
Ici, au lieu d’utiliser un type T comme argument de notre type g´en´erique, on utilise
une ”wildcard” <?extendsT > :
List <? extends Number> maliste = new ArrayList<Integer >() ;
List <? extends Number> maliste = new ArrayList<Float >() ;
List <? extends Number> maliste = new ArrayList<Double >() ;
Avec la covariance, nous pouvons lire des ´el´ements d’une structure, mais rien n’´ecrire
dedans. Ainsi, ”Number n = maliste.get(0) ;” est autoris´e.
En revanche ”maliste.add(3.14) ;” est refus´e. En effet, le compilateur ne peut d´eterminer le
type exact de l’objet dans la structure g´en´erique. Cela peut ˆetre n’importe quoi qui ´etend
Number, mais le compilateur ne peut en ˆetre certain. Ainsi, toute tentative de retrouver
une valeur g´en´erique est consid´er´ee comme une op´eration non sˆure et est donc rejet´ee
imm´ediatement par le compilateur.
2.2.2 La contravariance
Ici, on utilise une wildcard diff´erente : <?superT >. La contravariance nous permet de
faire l’op´eration oppos´ee. Nous pouvons lire dans une structure mais pas ´ecrire.
List<Object> myObjs = new List<Object () ;
myObjs . add ( ” Hello ” ) ;
myObjs . add ( ”World” ) ;
List <? super Number> nombres = myObjs ;
nombres . add (10) ;
Dans ce cas, nous pouvons bien ajouter un Number dans la liste nombres car Number
a pour ancˆetre Object. En revanche, ”Number n = nombres.get(0) ;” produit une erreur de
compilation. En effet nous ne sommes pas sˆurs `a 100% d’avoir un Number. Si le compilateur
laissait passer cela, nous pourrions avoir `a l’ex´ecution une ClassCastException. En somme,
nous utilisons la covariance quand nous voulons seulement lire une valeur g´en´erique dans
une structure et la contravariance quand on veut ´ecrire dedans. Un dernier exemple pour
illustrer :
6. 2.2 Variance 5
public s t a t i c void copy ( List <? extends Number> source ,
List <? super Number> destiny ) {
f or (Number number : source ) {
destiny . add (number) ;
}
}
List<Integer > myInts = asList (1 ,2 ,3 ,4) ;
List<Object> myObjs = new ArrayList<Object >() ;
copy ( myInts , myObjs) ;
Tous les types g´en´eriques sont ´etiquet´es comme inh´erents `a la covariance, contrava-
riance, bivariant ou invariant au type de leurs param`etres. Cette inh´erence peut donc ˆetre
employ´ee `a tous les types g´en´eriques.
Par exemple, nous pouvons changer de mani`ere raisonnable toutes les occurences de Iterator <
T > en Iterator <?extendsT > ou bien Comparator < T > en Comparator <?superT >.
Des chercheurs ont alors d´evelopp´e un outil afin d’am´eliorer cette g´en´eralisation. Il poss`ede
diff´erentes fonctionnalit´es :
1. Pour aider le programmeur `a utiliser la variance en Java, il permet de r´e´ecrire
automatiquement le code en un code avec des wildcards plus g´en´erales.
2. Cependant, tous les types ne peuvent pas ˆetre r´e´ecris, (ex : s’ils sont d´eclar´es dans
une tierce autre librairie o`u le code source n’est pas disponible). Ainsi l’utilisateur
peut choisir de ne pas r´e´ecrire le code, si garder un type sp´ecifique est pr´ef´erable
pour une future mise `a jour.
3. L’outil respecte la s´emantique Java et pr´eserve le comportement du programme.
7. Chapitre 3
Cas concret d’utilisation
Apr`es avoir les principes de la covariance et la contravariance, nous allons voir main-
tenant un exemple d’utilisation de l’outil pour renommer des entit´es d’un programme.
Prenons le programme suivant :
c l a s s WList<E> {
private List<E> elems = new LinkedList<E>() ;
void add (E elem ) {
addAll ( C o l l e c t i o n s . s i n g l e t o n L i s t ( elem ) ) ;
}
void addAll ( List<E> source ) {
addAndLog( source . i t e r a t o r () , t h i s . elems ) ;
}
s t a t i c <T> void addAndLog( Iterator <T> itr , List<T> dest ) {
while ( i t r . hasNext () ) {
T elem = i t r . next () ;
log ( elem ) ;
dest . add ( elem ) ;
}
}
De mani`ere g´en´erale, l’interface List est invariante. c’est `a dire qu’elle autorise la lecture
et l’´ecriture d’un ´el´ement. Or dans la m´ethode addAndLog, pour la liste dest rien n’est
lu. On ne fait qu’ajouter un ´el´ement avec add. On peut alors se limiter `a une version
contravariante de List en faisant un List <?superT > dest. Pour source, la seule m´ethode
invoqu´ee est iterator() qui retourne un Iterator < E >. Or Iterator est covariant comme
nous l’avons vu pr´ec´edemment. On peut alors de mani`ere sˆure inf´erer le type de source en
List <?extendsT >. Cependant, si on ne changeait que le type de source, le programme
ne compilerait pas. En effet, la m´ethode addLog attend un Iterator < T > mais on lui
fournit maintenant un Iterator <?extendsT > avec source. Une analyse du programme
est alors n´ecessaire pour savoir si la g´en´eralisation d’un type entraˆıne le changement de
type d’autres d´eclarations. Ce flot d’analyse doit prendre en compte les d´ependances entre
8. 7
chaque. Ici, nous pouvons alors changer le type de itr en Iterator <?extendsT >. Nous
obtenons alors :
c l a s s WList<E> {
private List<E> elems = new LinkedList<E>() ;
void add (E elem ) {
addAll ( C o l l e c t i o n s . s i n g l e t o n L i s t ( elem ) ) ;
}
void addAll ( List <? entends E> source ) {
addAndLog( source . i t e r a t o r () , t h i s . elems ) ;
}
s t a t i c <T> void addAndLog( Iterator <? extends T> itr , List <? super T> dest )
{
while ( i t r . hasNext () ) {
T elem = i t r . next () ;
log ( elem ) ;
dest . add ( elem ) ;
}
}
Apr`es avoir vu un exemple de son utilisation, nous allons maintenant ´etudier son fonc-
tionnement.
9. Chapitre 4
Le fonctionnement de l’outil
4.1 La syntaxe de la variance
L’outil de refractoring permet d’inf´erer la variance des types rencontr´es dans un pro-
gramme.
La variance dans le langage Java est implicite. En effet, il n’y pas de syntaxe pour que le
programmeur puisse d´efinir la variance d’un type.
Contrairement, au langage Scala o`u il est possible de d´efinir la covariance et la contrava-
riance par, respectivement, les annotations + et -. En Scala, par d´efaut, un ´el´ement est
invariant.
L’outil de refractoring va donc se baser sur un ensemble de r`egles et s’inspirer de la syntaxe
de Scala pour la d´efinition de la variance.
Intuitivement, la variance d’un ´el´ement est contraint par l’utilisation qu’on en fait. Par
exemple, le type des param`etres d’une m´ethode est g´en´eralement contravariant et le type
de retour d’une m´ethode est g´en´eralement covariant.
Ainsi, notons vx la variance de la variable X. Et, essayons de d´eterminer la variance de
quelques ´el´ements sur un exemple plutˆot simple :
i n t e r f a c e RList<X> { X get ( int i ) ; }
i n t e r f a c e WList<Y> { void set ( int i , Y y) ; }
i n t e r f a c e IList <Z> { Z setAndGet ( int i , Z z ) ; }
Dans l’interface RList, on remarque que la variable X est covariante car elle est le retour
de la m´ethode get. On note cela vx = + et se lit X est covariant dans RList.
Dans l’interface WList vY = - (contravariant) car Y est un des param`etres de la m´ethode
set.
Dans l’interface IList vz = o (invariant) car Z est `a la fois covariant et contravariant.
10. 4.2 L’analyse de l’influence des types 9
C’est de cette mani`ere que l’on peut d´eterminer la variance d’un ´el´ement `a partir d’une
d´efinition g´en´erique.
Apr`es avoir inf´erer une g´en´eralisation pour un type donn´e T, l’outil de refractoring permet
de remplacer le type T par sa g´en´eralisation.
En prenant l’exemple ci-dessous, l’outil de refractoring effectue les substitutions suivantes :
i n t e r f a c e RList<X> { X get ( int i ) ; }
i n t e r f a c e WList<Y> { void set ( int i , <? super Y> y) ; }
i n t e r f a c e IList <Z> { Z setAndGet ( int i , Z z ) ; }
4.2 L’analyse de l’influence des types
4.2.1 Le principe
Le fait de g´en´eraliser les types d’un programme implique de nombreux compromis. En
effet, plusieurs probl`emes apparaissent lors de la g´en´eralisation d’un type :
1. G´en´eraliser une List < String > par une List <? extends String > pose un certain
probl`eme. C’est un exemple qui montre qu’en Java, il est impossible de red´efinir
La classe String. Il faut donc se poser la question des classes, des m´ethodes ...
immuables en Java.
2. La red´efinition de m´ethode est possible uniquement si les param`etres de la m´ethode
du fils sont identiques `a celle du p`ere. Ainsi, lorsque l’on g´en´eralise les types des pa-
ram`etres d’une m´ethode fils, Java ne consid`ere plus cette m´ethode comme red´efinition
de m´ethode de la m´ethode p`ere. Or, on voudrait que ¸ca soit le cas.
3. La g´en´eralisation d’un type peut amener `a g´en´eraliser d’autres ´el´ements qui sont
plus ou moins d´ependant de ce type. L’outil de refractoring ne doit pas ajouter
d’erreur de compilation et ne doit pas modifier la s´emantique du programme.
Ces probl`emes sont r´esolus en r´ealisant un graphe orient´e des influences sur les d´eclarations
dans le programme.
Ainsi, l’outil de refractoring construit un graphe d’influence ; Pour chaque d´ependance entre
les entit´es (variable, signature de m´ethode ...) A et B, il existe une arˆete qui lie les nœuds
correspondant aux entit´es A et B dans ce graphe.
Par la suite, lorsque l’on g´en´eralise l’entit´e A, on parcourt le graphe d’influence en g´en´eralisant
les noeuds connexe au noeud de l’entit´e A.
Pour pallier aux soucis 1, l’outil de refractoring d´ecide quels ´el´ements ne peuvent pas
ˆetre g´en´eralis´es.
Concernant le probl`eme 2, l’outil va consid´erer que la signature des param`etres de la
11. 4.2 L’analyse de l’influence des types 10
m´ethode m du fils est fortement d´ependante de la signature de la m´ethode m du p`ere.
Dans le graphe d’influence, on va donc ajouter une arˆete entre la signature de la m´ethode
p`ere et celle du fils.
Pour donner une exemple clair au probl`eme 3, si on g´en´eralise le type de retour d’une
m´ethode M alors les variables qui sont affect´ees par la valeur du r´esultat de la m´ethode M
doivent ˆetre g´en´eralis´es. Il y aura donc un chemin dans le graphe d’influence qui va lier la
m´ethode M et les variables affect´ees par la valeur de M.
Prenons, un autre exemple, si l’on g´en´eralise les param`etres p1 et p2 d’une m´ethode M. Et
si dans le corps de cette m´ethode M, un objet O fait un appel `a la m´ethode O.m(p1, p2).
Alors il faudra g´en´eraliser la signature de la m´ethode O.m. Il existe donc un chemin dans
le graphe d’influence entre l’objet O et les param`etres p1 et p2.
4.2.2 L’algorithme
Les noeuds du graphe d’influence poss`ede la syntaxe abstraite suivante :
Figure 4.1 – Syntaxe du graphe d’influence
FieldDeclaration est la repr´esentation des variables de classes ou d’objet.
V ariableDeclaration correspond `a la d´eclaration de variables locales.
ParameterDeclarations correspond aux param`etres des m´ethodes ou des constructeurs.
MethodDecl correspond au type de retour d’une m´ethode.
On va ´egalement d´efinir un langage pour d´ecrire les programmes Java. Ce langage nous
permettra de d´efinir des fonctions qui nous seront utiles pour l’algorithme d’analyse de l’in-
fluence des types et permet d’abstraire la complexit´e de la repr´esentation d’un programme
Java.
12. 4.2 L’analyse de l’influence des types 11
Figure 4.2 – Mini langage
Avec cette syntaxe, on va pouvoir expliciter la variance des types par v ou w. Et, le
et T sont respectivement la d´enotation du mot cl´e extends de Java et d’un tableau de
T1, T2, ...Tn
D´efinissons les r`egles des fonctions nodesAffectingType(e) et destinationNode(e) en
utilisant ce langage :
Figure 4.3 – Fonctions utilis´es par l’algorithme
13. 4.2 L’analyse de l’influence des types 12
Explicitons ces fonctions.
nodesAffectingType(e) permet de retrouver l’ensemble des d´eclarations accesibles dans e
qui peut modifier le type de e. Et, destinationNode(e) donne l’ensemble des d´eclarations
d´ependant de e. Ces fonctions seront tr`es utilis´ees dans l’algorithme.
Pour chacune de ces fonctions, on a d´efini trois r`egles. Par exemple, la r`egle N-MonoMethod
d´eclare que si le retour de la m´ethode m ne d´epend pas de e alors seule la d´eclaration de
m peut affecter le type < T > m < e >.
Parlons de l’algorithme de cr´eation du graphe d’influence. L’algorithme effectue trois pas-
sages sur le programme pour analyser :
1. Les appels de m´ethode : pour chaque appel de m´ethode < T > m < e > , on va
retrouver les d´eclarations de e dans le programme. On va ´egalement retrouver les
m´ethodes qui prennent exactement e en param`etre. Et, on va rajouter une arˆete
entre e et les param`etres des m´ethodes trouv´ees pr´ec´edement.
2. Les expressions : pour chaque expression e , on cherche les d´eclarations D qui sont
affect´ees par e. Et, pour chaque d´eclaration N qui est accessible par e et qui peut
affecter e, on relie dans le graphe d’influence N et D.
3. Les d´eclarations de m´ethode : pour chaque d´eclaration de m´ethode M, on va trouver
les m´ethodes M qui red´efinissent ou sont r´ed´efinies par M. On va ajouter une arˆete
dans le graphe entre les param`etres des methodes M et M .
L’ensemble de ces r`egles permettent de construire le graphe d’influence d’un programme
Java Voici un pseudo code de l’algorithme :
14. 4.2 L’analyse de l’influence des types 13
Figure 4.4 – Pseudo code de l’algorithme du graphe d’influence
15. Chapitre 5
Les Applications de l’outil
Apr`es avoir vu comment fonctionnait l’outil, nous pouvons voir que plus le nombre de
d´eclarations augmente dans le graphe, et plus le nombre de d´eclarations immuables fait
de mˆeme. Ainsi, moins de d´eclarations seront r´e´ecrites parce qu’il existera plus d’arˆetes
o`u les r´e´ecritures sont interdites dans le graphe. Pour palier ce probl`eme, les chercheurs
ont pens´e qu’il fallait que l’analyse du programme ignore les d´eclarations qui ne peuvent
ˆetre aff´ect´ees par la g´en´eralisation, c’est `a dire ne pas les mettre dans le graphe. Voyons
certains exemples de types que le flot d’analyse ignore :
1. Les types primitifs tels que les int, char, boolean ainsi que les types monomorphiques
comme String et Object. Ces types ne peuvent pas ˆetre modifi´es avec des wildcards.
2. Les types param´etriques qui sont sp´ecifi´es bivariant ( ¡ ?¿ ). Ces types ne peuvent
pas ˆetre plus g´en´eralis´es qu’ils ne le sont d´ej`a.
L’outil permet donc de g´en´eraliser des classes en s´el´ectionnant quelles d´eclarations
parmi les variables locales, les arguments ou retour de m´ethodes lesquels sont `a renommer.
Des travaux pr´ec´edents ont montr´e que 53% des interfaces et 37% des classes peuvent ˆetre
g´en´eralis´ees. Ceci montre alors l’impact que pourrait avoir cet outil si toutes ces d´eclarations
´etaient r´e´ecrites. Il aiderait ´egalement les utilisateurs qui n’ont pas de grandes notions de
variance `a am´eliorer les performances de leur code. Afin d’´evaleur le potentiel de cet outils,
des statistiques ont permis de calculer combien de d´eclarations de types param´etriques
pouvaient ˆetre r´e´ecrites. L’outil `a ´et´e test´e sur six librairies java. Nous n’en montrerons
que trois ici :
16. 15
Figure 5.1 – Statistiques des r´e´ecritures de toutes les d´eclarations pour les types
g´en´eriques
Prenons ici l’exemple des interfaces java, il existe 170 interfaces avec des types pa-
ram´etr´es. Parmi celles l`a, 148 peuvent ˆetre r´e´ecrites. Apr`es le passage de l’outil, 34 ont ´et´e
r´e´ecrites, soit 20%. Sur l’ensemble des six librairies test´ees, nous obtenons un total 12% de
r´e´ecritures en prenant compte les classes et les interfaces, sur un potentiel de 73%. Cela
repr´esente tout de mˆeme 2220 sur 18259 r´e´ecritures.
17. 16
Figure 5.2 – Statistiques des r´e´ecritures de toutes les d´eclarations pour les types variant
Pour les types variant, nous voyons que nous obtenons de bien meilleures performances.
Sur l’ensemble des six librairies, on obtient un total de 34% de renommage.
De plus, la r´e´ecriture seule de JDK ne prend que deux minutes pour 198 milles lignes de
code.
18. Chapitre 6
Conclusion
En conclusion, apr`es avoir vu quelques exemples d’utilisations ainsi que son fonctionne-
ment, nous pouvons voir que cet outil complexe nous permet d’am´eliorer les perfermances
de notre code en g´en´eralisant le plus possible les types param´etriques `a l’aide de wildcards.
Cependant, en regardant les statistiques sur le pourcentage de renommage apr`es le pas-
sage de l’outil sur les six principales librairies Java, 34% des g´en´eriques ont pu ˆetre plus
sp´ecifiques avec des wildcards plus g´en´eralis´ees. Nous pouvons alors penser que cet outil
peut encore ˆetre am´elior´e. Des travaux futurs ont ´et´e evoqu´e dans ce sens.