OK - LRI

publicité
Année 2016-2017
Polytech’Paris-Sud – 4ème année
Département Informatique
« Polymorphisme et Généricité en
Java »
Frédéric Voisin
Département Informatique
Co-variance vs Contra-variance (rappel !)
Soit Figures Id (X: Figures ) { … } dans Figures
Dans Cercle on redéfinit la méthode Id en gardant son en-tête:
Figures Cercle::Id (X: Figures ) { … } -- OK
Peut-on lors d’une redéfinition spécialiser/élargir le type des
paramètres ou du résultat ?
Id
Id
Id
Id
(X:
(X:
(X:
(X:
Cercle) return Figures is …
Figures) return Cercle is ...
FigureAbstraite) return Figures is …
Figures) return FigureAbstraite is …
-----
KO
OK
OK
KO
Co-variance pour le résultat (ex : clone() en Java)
Contra-variance pour les paramètres (pas implémenté en Java, vu
comme une surcharge)
2016-2017
La généricité en Java
2
Polymorphisme paramétrique à la Ada (rappel)
paramètres
generic
type Elem is private;
type Table is array(integer range <>) of elem;
with function "<="(E1,E2 : Elem) return boolean;
procedure Tri(t: in out Table); – tri générique...
Idée ? Pouvoir paramétrer une procédure ou un type par :
des types, des procédures, des fonctions
préciser les « propriétés » attendues de ces paramètres
Instantiation explicite à la Ada ou implicite à la C++ pour associer
un composant concret à un paramètre de généricité.
procedure TriIntDecr is
-- à la Ada
new Tri(Elem => integer, "<=" => ">=", Table => TableInt);
2016-2017
La généricité en Java
3
La généricité à partir de Java 5.0
public class Paire<T1, T2> { // détail dans le prochain transparent
protected T1 first;
protected T2 second;
public Paire(T1 a, T2 b) { first = a; second = b; }
…
}
Paire<String, Integer> maPaire = new Paire<String, Integer>("a", 1);
Paire<String, Integer> maPaire = new Paire<>("a", 1); // Java 7 !
Contraintes possibles sur le paramètre :
public class C <T extends T1 & I1 & I2> { … }
C<A> monObj;
// OK si A sous-classe de T1 et implémente I1 et I2
Au plus une classe, forcément en tête, et des interfaces arbitraires;
Si seulement des interfaces, l'ordre importe (voir après) !
2016-2017
La généricité en Java
4
Une classe générique simple
public class Paire<T1, T2> {
private T1 first; private T2 second;
public Paire() { first = null; second = null; }
public Paire(T1 a, T2 b) { first = a; second = b; }
public T1 first() { return first; }
public T2 second() { return second; }
public void setFirst(T1 val) { first = val; }
public void setSecond(T2 val) { second = val; }
… etc …
}
Ici, simple car il n'y a pas de contraintes sur T1 et T2.
En pratique, moins simple à concevoir qu'on croit, si on veut
qu'elle soit réellement réutilisable
2016-2017
La généricité en Java
5
Autres éléments génériques Java 5.0
Interfaces génériques
interface Comparable<T> { int compareTo(T o); }
public class C implements Comparable<C> { int compareTo(C c) {...} }
Méthodes génériques (ici supposées dans une classe Utilitaires)
public static <T> T anyOne(T[] tab)…
// version provisoire de l'en-tête …
public static <T extends Comparable<T>> T max(Collection<T> c)
String[] maTable = { "Hello", "world" };
Collection<String> l = Arrays.asList("Hello", "world"); //Java 7
String s = Utilitaires.<String>anyOne(maTable);
String s = Utilitaires.max(l);
// <String> implicite
2016-2017
La généricité en Java
6
La généricité en Java : Principes de base
Renforcer le typage statique et diminuer le besoin en transtypage explicite
Compatibilité avec le code existant, d’où sous-typage « osé » avec
contrôle dynamique à l’exécution et compatibilité avec la JVM
Les librairies ont été mises à jour pour être génériques.
La généricité est traitée uniquement à la compilation
Pas de classe générique à l'exécution : la JVM reste non générique.
Le compilateur convertit les génériques en non-génériques
●
Ajout des contrôles statiques
●
Ajout automatique et silencieux de transtypages
Paire<String, String> ps; ps.setFirst(e); String s = ps.first();
À l’exécution une seule version de la classe, basée sur le « type limitatif »
du paramètre : les paramètres de type sont « effacés » !
Différent donc du modèle Ada/C++ :
Dynamiquement, il existe une unique version non générique
2016-2017
La généricité en Java
7
Exemples d'effacement de type
class ArrayList<E>
implements List<E>,
Cloneable
{ … }
ArrayList<E> où E est une classe arbitraire:
la version « effacée » est équivalente à ArrayList<Object>
public class Paire<T1, T2>: T1 et T2 n'ont pas de contraintes.
la version « effacée » est Paire i.e. Paire<Object, Object>
public class C <T extends C1 & I1 & I2, … > { … }
la version « effacée » est C, qui représente C<C1>
public class Intervalle <T extends Comparable> { … }
la version « effacée » est Intervalle, i.e. Intervalle<Comparable>
C++: Paire ou Paire<T1, T2> n’aurait pas de sens à l’exécution !
Java : Paire est une classe standard. Dynamiquement il existe une
unique version non générique !
Paire p;
– compile, mais représente quoi ?
☞ ArrayList ≠ ArrayList<Object> ≠ ArrayList< ? >
2016-2017
La généricité en Java
8
Polymorphisme générique et par inclusion (rappel)
Figure
Cercle
2016-2017
Set
Set
Figure
X
Set Cercle
La généricité en Java
Java 1.5 :
comme C++
9
Polymorphisme générique et par inclusion (2)
Syntaxe à la Java mais même problème dans tout langage avec
héritage+généricité
public class Set<Elem> {
public Set() { … }
public void insert(Elem e){ … }
… etc …
};
par instantiation du paramètre, on peut obtenir les classes:
Set<Figure>
// définit void insert(Figure e) { … }
Set<Cercle>
// définit void insert(Cercle e) { … }
static void teste(Set<Figure> s) {
s.insert(new Rectangle());
}
teste(new Set<Cercle>());
// OK ou KO ?
// OK ou KO ?
Avec OK partout on aurait une absurdité => où doit-on dire KO ?
2016-2017
La généricité en Java
10
Polymorphisme générique et par inclusion (3)
Figure
Set
Set
Cercle
X
Set Cercle
Figure
Cercle
Set
Set
X
Set
2016-2017
Figure
Figure
Java 1.5 ?
Comme en C++
Java 1.5 : OK ?!
Set<Figure> et Set<Cercle>
n’existent pas dynamiquement
mais Set existe !
Set Cercle
La généricité en Java
11
Le poids du passé ...
Java 5 : compatibilité délicate avec le code existant non générique
static ArrayList f(ArrayList arg) { return arg; } // Java 4
ArrayList<Integer> a1 = new ArrayList<Integer>();
ArrayList<Object> a2 = new ArrayList<Object>();
a1 = f(a1); // OK ?
a2 = f(a1); // OK ?
a2 = a1;
// OK ?
a1 = a2;
// OK ?
a1 = (ArrayList<Integer>) a2; // OK ?
a1 = f(a2); // OK ?
a2.add("Sad world"); Integer i = a1.get(0);
Dans la suite on s'en sert pour illustrer certains aspects !
Pas de problème de type si on ne mélange pas Java4- et Java5+
2016-2017
La généricité en Java
12
Cas particuliers des tableaux (rappel)
Figure[] tf = new Figure[2];
Cercle[] tc = new Cercle[2];
Rectangle r = new Rectangle(…);
Figure f ;
tf[0] = r;
// OK
tf = tc;
// OK en Java ! tf référence donc un tableau de cercles...
tf[0] = r;
tf[0] = f;
// KO à l’exécution(ArrayStoreException)
// KO possible à l’exécution(ArrayStoreException)
Les tableaux mémorisent dynamiquement le type d’éléments stockés.
Contrôle explicite à l'exécution… et risque de levée d'exception !
Était-ce une bonne idée d'autoriser tf = tc ?
La notion de tableaux est vue comme une opération covariante en Java
2016-2017
La généricité en Java
13
Quelques restrictions sur les génériques
Pas de classe générique d’exceptions.
Pas de type primitif dans les instanciations :
Paire<int, double> p;
// KO : serait Paire<Object, Object>
Paire<Integer, Double> p; // OK
Inconvénient allégé par « boxing/unboxing » automatique !
Perte de fiabilité des informations dynamiques de type
ArrayList<Integer> ai = new ArrayList<Integer>();
ArrayList<String> as = new ArrayList<String>();
ArrayList a = ai;
// on passe par du Java4ai = (ArrayList<Integer>) a; // OK ?
as = (ArrayList<String>) a; // OK ?
as = (ArrayList<String>) ai; // KO !
!
instanceof est interdit sur les classes paramétrées : dynamiquement,
pas de différence entre Paire<String,String> et Paire<Integer,Integer>
2016-2017
La généricité en Java
14
Quelques restrictions (suite)
Pas de tableaux d'instantiation de génériques
Paire<String, Integer>[] t = …
// KO
pb avec l'effacement de types et la gestion de ArrayStoreException
Pas d’objet d’un type paramètre
public class Paire<T1, T2> {
public static Paire<T1, T2> getFirst() {
return new T1(...); // le new est dynamique : créerait quoi ?
}
}
Pas de variable de type générique pour méthodes ou attributs static :
public class Singleton<T> {
static T theInst;
// KO.
static T getInst() { … }
// KO.
}
Ferait un drôle de Singleton après instantiation !
2016-2017
La généricité en Java
15
Retour sur un exemple simple...
public class Paire<T1, T2> {
private T1 first;
private T2 second;
public Paire() { first = null; second = null; }
public Paire(T1 a, T2 b) { first = a; second = b; }
public T1 first() { return first; }
public T2 second() { return second; }
public void setFirst(T1 val) { first = val; }
public void setSecond(T2 val) { second = val; }
}
Que se passe-t-il avec l'effacement de type ?
2016-2017
La généricité en Java
16
Après effacement ...
Tout se passe comme si dans la JVM il y avait en fait:
public class Paire {
private Object first;
private Object second;
public Paire() { first = null; second = null; }
public Paire(Object a, Object b){ first=a; second=b;}
public Object first() { return first; }
public Object second() { return second; }
public void setFirst(Object v) { first = v; }
public void setSecond(Object v) { second = v; }
}
sauf que le compilateur ajoute contrôles et transtypages
dans votre code pour garantir le typage sûr.
2016-2017
La généricité en Java
17
Une utilisation de Paire
static int teste(Paire<Integer, Integer> pi) {
return pi.first() + pi.second();
// Compile ?
}
static void t2(Paire<Integer, Integer> pi) { pi.second(); }
public static void main(String[] argv) {
Paire<Integer> pi = new Paire<Integer, Integer>(1, 2);
Paire p = pi ;
// OK !
teste(pi);
pi.setSecond("Coucou");
// KO à la compilation
p.setSecond("Coucou");
// OK ou KO ?
teste(pi);
// OK ou KO ?
t2(pi) ;
// OK ou KO ?
}
La « vraie » forme de setSecond à l'exécution est :
public void setSecond(Object val) { second = val; }
2016-2017
La généricité en Java
18
Une sous-classe de Paire
public class PaireInteger extends Paire<Integer, Integer> {
public setSecond(Integer i) { // redéfinition ou pas ?
…
super.setSecond(2*i) ;
}
}
Avec l'effacement de types, s'agit-il d'une redéfinition ou une surcharge ?
Dans la JVM pour PaireInteger il y a deux méthodes setSecond :
public void setSecond(Object v) – héritée de Paire<Integer,Integer>
public void setSecond(Integer i)– locale !
PaireInteger pi = new PaireInteger();
Paire p = pi ;
– OK ?
p.setSecond(1);
– Quelle méthode setSecond ?
System.out.println(pi.first() + pi.second()); – OK ?
Bien sûr, si on ne passe jamais par Paire on ne se pose pas la question.
2016-2017
La généricité en Java
19
Méthodes « bridges » (1)
But : Régler le problème lié à l'effacement de type et assurer la
cohérence avec le polymorphisme...
Pour rétablir le polymorphisme, le compilateur ajoute une méthode
(« bridge ») dans la classe PaireInteger qui redéfinit la version
qu'on hériterait par défaut de la classe générique et fait le lien avec
la méthode surchargée!
public void setSecond(Object v) {
this.setSecond((Integer) v); // Cast risqué ou pas?
}
Méthode ajoutée automatiquement par le compilateur, non
référençable par le programmeur (n’existe que dans le bytecode,
mais visible via introspection)
2016-2017
La généricité en Java
20
Méthodes « bridges » (2)
Même problème pour gérer la covariance du type de retour !
public class A { public A f() { return new A(); }
public class B extends A {
public B f() {
B res1 = this.f();
A res2 = super.f();
B res3 = ((A) this).f();
A res4 = ((A) this).f();
}
// Redéfinition? Surcharge ?
// Boucle à l’exécution: Normal.
// OK, version A de f appelée
// Compile pas: OK.
// Compile mais exécute quoi ?
// “caster” est une notion statique !
}
}
Génération automatique d’une méthode “bridge” dans B
public A f() { return this.f();}
Deux méthodes f() sans paramètre dans (la version JVM de) B:
une à valeurs dans A, l'autre à valeurs dans B.
2016-2017
La généricité en Java
21
Écriture de méthodes/classes génériques
La « non-compatibilité » entre instantiation et héritage complique
l’écriture de méthodes réutilisables …
static void dessineTout (Collection<Figure> c) {
for(Figure f: c) f.dessine();
// OK
}
Collection<Cercle> ac = new ArrayList<>();
// Java 7.0
dessineTout(ac);
// KO !
dessineTout((Collection<Figure>) ac);
// KO !
Pourtant dessineTout ne met pas en danger le typage
Avec les librairies génériques (Collection, Comparable, etc.), cela
reste généralement transparent !
Merci les concepteurs des bibliothèques !
Attention à la spécification parfois surprenante des paramètres ;-|
Il faut des mécanismes pour différencier les cas « problématiques »
2016-2017
La généricité en Java
22
Tentatives de solutions…
Retour à la version 4 ? Perte d'information statique = risque
d'exceptions dynamiques
public static void dessine (Collection c) {
for(Object o: c) ((Figure) o).dessine() ;
}
Passage par une procédure générique ?
public static <T extends Figure> void dessine (Collection<T> c)
{ for(Figure f: c) f.dessine(); }
artificiel : dessine n'a pas besoin d'être générique.
☞
Introduction de « joker » (wildcard) dans la description des
paramètres ou du résultat d'une méthode pour mieux faire
cohabiter instantiation et héritage !
2016-2017
La généricité en Java
23
Types « Joker »
Possibilité d’avoir des types « joker » bornés inférieurement ou
supérieurement :
< ? > correspond à « n’importe quel type » (mais pas un type précis
comme avec une méthode générique)
< ? extends Figure> n’importe quel sous-type de Figure (inclus)
< ? super Figure> n’importe quel super-type de Figure (inclus)
Différentes usages/limitations associés à ces types joker
Notions similaires en Scala, liées à covariance/contravariance
2016-2017
La généricité en Java
24
Hiérarchies de sous-typage avec Joker…
Collection
Collection<Object>
Collection<?>
Collection<? extends Figure>
super: contravariant !
Collection<? super Figure>
extends : covariant !
Collection<Cercle>
Collection<Figure>
: « est sous-type de »
List<Cercle>
List<E> extends Collection<E> { … }
2016-2017
La généricité en Java
– OK !
25
Hiérarchies de sous-typage avec Joker…
ArrayList
ArrayList<?>
class ArrayList<E>
implements Collection<E> {
boolean add(E elt);
E get(int i) ;
}
Compatibilité avec Java 1.4 : typage et contraintes plus « souples » pour
ArrayList que pour ArrayList<?> et ArrayList<Object>
public static void f(ArrayList<?> arg, Object o) {
arg.add(o);
// KO: pas de type spécifique à la place de E
}
public static void f(ArrayList arg, Object o) {
arg.add(o);
// perte d’information statique
// donc recours à des cast… comme en 1.4
}
Distinction entre positions covariantes et contra-variantes...
2016-2017
La généricité en Java
26
Restrictions pour les Joker
f(ArrayList<? extends Figure> arg)
class ArrayList<E>
implements Collection<E> {
boolean add(E elt);
E get(int i) ;
}
Si une méthode de ArrayList<E> prend un paramètre de type E on ne peut pas
passer un type spécifique (Figure, Cercle, …) dans la version instantiée.
Par contre si une méthode renvoie un résultat de type E, on peut l’utiliser là où
on peut utiliser une instance de type Figure !
f(ArrayList < ? extends Figure> arg): covariant => interdit l'usage de arg
en position contravariante (donc comme paramètre).
« ? extends Figure » représente un sous-type inconnu de Figure et ne peut
pas correspondre à une instance d’un type spécifique. On ne peut que parcourir la
collection, pas la modifier (sauf par des méthodes comme clear qui ne se soucient
pas du type des objets stockés)
Dans Collection de nombreuses méthodes (contains, remove, …) ne prennent
par un E en paramètre mais un Object. Donc « ça compile» !
public static void f(ArrayList<Figure> a, Object o) { a.contains(o);}
2016-2017
La généricité en Java
27
Restrictions pour les Joker (2)
ArrayList<? super Figure> : restrictions duales… Pas d'usage en
position contravariante, seulement en position covariante.
Ajouter à une ArrayLst les éléments d’une ArrayList :
static void toList(ArrayList<? extends Figure> src,
ArrayList<? super Figure> dst) {
for(Figure f: src) dst.add(f); // OK.
}
On peut parcourir src et modifier dst !
ArrayList<Cercle> ac = new ArrayList<Cercle>();
Array<Object>
os = new ArrayList<Object>();
ac.add(new Cercle()); ac.add(new Cercle());
os.add("coucou"); os.add(1);
toList(ac, os);
// OK : introduit les deux cercles dans os.
2016-2017
La généricité en Java
28
Joker ? Méthode paramétrée ?
static <T> ArrayList<T> add(ArrayList<T> al,
ArrayList<? super T> l) {
pour l'instant l ne sert pas !
ArrayList<T> r = new ArrayList<T>();// T ? viendra d'où ? Compile ?
for(T e: al) r.add(e);
// Compile ?? à l’exécution ??
return r;
}
static <T> ArrayList<T> add2(ArrayList<? extends T> al,
ArrayList<T> l){
ArrayList<T> r = new ArrayList<T>(); // T ? viendra d'où ? Compile ?
for(T e: al) r.add(e);
// Compile ??
return r;
}
ArrayList<Figure> af = new ArrayList<>();
ArrayList<object> ao = new ArrayList<>();
af.add(New Cercle()); af.add(new Rectangle());
ao.add("coucou"); ao.add(1);
2016-2017
La généricité en Java
29
Pour voir si on a compris ;-(
Qu’est-ce qui est correct ?
Il faut vérifier les appels à add et add2 ainsi que l’affectation
ArrayList<Figure> s1 = add(af, ao);
// OK ou KO ?
ArrayList<Object> s2 = add(af, ao);
// OK ou KO ?
ArrayList<Figure> s3 = add2(af, ao);
// OK ou KO ?
ArrayList<Object> s4 = add2(af, ao);
// OK ou KO ?
2016-2017
La généricité en Java
30
interface Comparator<E> {
class TreeSet<E> {
? super again int compare(E o1, E o2);
TreeSet(Comparator c) …
}
}
TreeSet : Nécessite une relation d’ordre sur E ou un comparateur de E
Usage ? TreeSet<Cercle> s = new TreeSet(unComparateur);
Quels comparateurs peut-on utiliser pour unComparateur ?
☞ Ça dépend de ce qu’a prévu le concepteur de TreeSet !
Version naïve : TreeSet(Comparator<E> c) { … }
☞ int compare(Cercle o1, Cercle o2);
N'accepte que les comparateurs entre cercles :-(
Version améliorée : TreeSet(Comparator<? super E> c) { … }
accepte aussi un comparateur plus général qu'on aurait défini:
int compare(Figure o1, Figure o2);
ou
2016-2017
int compare(Object o1, Object o2);
La généricité en Java
31
max revisitée (cf transparent 7)
static <T extends Comparable<T>> T max(Collection<T> tab) { … }
Trop restrictif : impose à C une méthode int compareTo(C arg)
alors qu’on dispose peut-être dans C de int compareTo(Object arg)
public class C implements Comparable { // ou Comparable<Object> ?
public int compareTo(Object o) { … }
}
Collection<C> maTab = … ; C monC = max(maTab);
// KO
Une meilleure interface de max ?
static <T extends Comparable<? super T>> T max(Collection<T> tab)
L’interface exacte de max ?
static <T extends Object & Comparable<? super T> >
T max(Collection<? extends T> tab)
Sinon la version « effacée » serait : Comparable max(Collection arg)
La généricité
en:Java
or 2016-2017
la version Java 4 a pour
en-tête
Object max(Collection arg)
32
Pour résumer (Java)
Les librairies sont bien conçues et peuvent être réutilisées
facilement mais …
Paramètres parfois surprenants : ex. dans Collection<E>
méthode contains(Object e) plutôt que contains(E e)
Méthodes « bridges » qui peuvent être nécessaires pour avoir
le « comportement attendu » du polymorphisme
Le mélange avec du code 4.0 réintroduit les risques
d'exceptions à l'exécution
Concevoir une classe générique réutilisable ?
Nécessite de bien maîtriser les types « Joker »
Méthode générique d’une classe standard
vs méthode standard d’une classe générique ?
2016-2017
La généricité en Java
33
Que manque-t-il encore ?
Passage de méthodes en paramètre (« Lambda expressions » Java 1.8)
Plus de flexibilité pour gérer co- et contra-variance à la Scala.
Figure
Cercle
Set
Set
Figure
X
Set Cercle
Le problème vient que insert de Set<Figure> peut insérer une
Figure arbitraire qui polluerait un Set<Cercle>
Plus de modification d’un ensemble => plus de problème !
Déclaration de la variance (l'usage attendu !) au niveau du paramètre,
pas globalement ! Par exemple, décrire l'usage effectué de c dans
dessineTout(Collection<Figure> c)
2016-2017
La généricité en Java
34
Téléchargement