précédent | suivant | table des matières | s'évaluer |
|
La généricité permet de définir des classes, et des méthodes paramétrées par une ou plusieurs classes.
1 Pourquoi la généricité ?
Exemple : Soit la classe suivante :
public class Paire {
Object premier ;
Object second;
public Paire (Object a, Object b){
premier= a; second = b;
}
public object getPremier(){
return premier;
}
public object getSecond(){
return second;
}
Les deux inconvénients sont :
Paire p = new Paire ("abc", "xyz");
String x = (String)p.getPremier(); // le casting est obligatoire
Double y = (Double)p.getSecond(); // Il faut attendre l'exécution pour avoir
// une levée d'exception(ClassCastException)
On définit alors une classe paramétrée :
public class Paire<T> { T premier ; T second; public Paire (T a, T b){ premier a; second = b; } public T getPremier(){ return premier; } public getSecond(){ return second; } }
Le programme est alors plus simple et plus sur :
Paire<String> p = new Paire<String> ("abc", "xyz");
String x = p.getPremier(); // pas de cast
Double y = p.getSecond(); // erreur de compilation (type mismatch)
2 Méthode générique
On peut définir une méthode générique dans une classe de la façon suivante :
public class X { public <T> void affiche(Paire<T> p){ System.out.println(p); } public <T> T choix(T a, T b){ return (int)(Math.random()*2)==1?a:b; } public static void main(String []a){ Paire<String> ps = new Paire<String>("un", "deux"); Paire<Integer> pi = new Paire<Integer>(1, 2); X x = new X(); x.affiche(ps); x.affiche(pi); Number n = x.choix(new Integer(2), new Double(3.14159)); } }
Dans la dernière ligne, le compilateur fait une inférence de type, et calcule la première super classe commune aux deux classes Integer et Double, soit Number.
3 Limite pour les types paramètre
Supposons que nous voulions ajouter à la classe Paire<T> la méthode suivante :
public T min(){ if(premier.compareTo(second)<=0) return premier; else return second; }Le compilateur signale alors que la méthode compareTo n'est pas définie pour le type T. Il faut restreindre T à une classe qui a cette méthode, et définir la classe Paire de la façon suivante :
public class Paire<T extends Comparable> { T premier ; T second; public Paire (T a, T b){ premier a; second = b; } public T getPremier(){ return premier; } public getSecond(){ return second; } public T min(){ if(premier.compareTo(second)<=0) return premier; else return second; } }Le type limitant peut être une classe ou une interface. On définira :
public class Paire<T extends Number> { ... }pour définir une paire de nombres... et
public class Paire<T extends A & Comparable> { ... }pour définir une paire d'élément de la classe A ou de classes dérivées de A et qui sont Comparable.
4 Effacement
Les classes paramétrées sont compilées vers une classe représentant le type brut : le type équivalent débarrassé des paramètres de la classe. On dit que les paramètres de type sont effacés. Ceci a des conséquences :
Paire<String, Integer> p1 = new Paire<String>("abc"); if(p1 instanceof Paire) ; // OK if(p1 instanceof Paire<String>) ;// NON OKLa dernière ligne provoque l'erreur de compilation suivante : Impossible d'effectuer une vérification instanceof sur le type paramétré Paire
public class Paire { Object premier ; Object second; public Paire (Object a, Object b){ premier= a; second = b; } public object getPremier(){ return premier; } public object getSecond(){ return second; }
public class Paire { A premier ; A second; public Paire (A,A b){ premier= a; second = b; } public A getPremier(){ return premier; } public A getSecond(){ return second; }
Paire<String> p1 = new Paire<String>("abc", "1"); Paire p2 = new Paire("abc", "def"); p2 = p1; // OK p1 = p2; // avertissement, sécurité de type : // l'expression du type Paire requiert une conversion non controlée en Paire<String>
5 Généricité et héritage
Soient les deux classes suivantes :
public class Super {
...
}
|
public class Sous extends Super {
...
}
|
Il n'existe pas de relation d'héritage entre Paire<Super> et Paire<Sous>.
Paire<Super> pSup = new Paire<Super>(new Super(), new Super());
Paire<Sous> pSous = new Paire<Sous>(new Sous(), new Sous());
pSup = pSous ; // INTERDIT
Supposons que celà soit possible,nous pourrions alors écrire :
pSup.setPremier(new Super());
Or pSup et pSous désigne le même objet et l'objet désigné par pSous aurait son attribut premier de type Super !!!
6 Joker
Supposons que nous définissions dans la classe X une autre méthode pour afficher des paires de Super, de la façon suivante :
public void afficheS(Paire<Super>p){
System.out.println(p);
} |
||||
Paire <Super> ps = new Paire<Super>();
Paire <Sous> pso = new Paire< Sous>();
|
Pour que la dernière instruction soit possible il faudrait définir la méthode afficheS de la façon suivante :
public void afficheS(Paire<?> p){
System.out.println(p);
}
Mais dans ce cas tout est possible, et si on veut se limiter aux classes qui dérivent de Super :
public void afficheS(Paire<? extends Super>p){
System.out.println(p);
}
Les jokers peuvent être limités vers le haut (extends) ou vers le bas (super).
On a les relations d'héritage suivantes :
La relation d'héritage entre Paire et Paire<?> est là pour assurer la compatibilité avec les codes existants avant la version 5 de Java.
7 Capture du Joker
Soit la classe :
public class Paire<T> { T premier ; T second; public Paire (T a, T b){ premier a; second = b; } public T getPremier(){ return premier; } public getSecond(){ return second; } public void setPremier(T t){ premier = t; } }On a alors :
Paire<?> p1 = new Paire<Integer>( 1, 2); Paire<? extends Number> p2 = new Paire<Integer>( 1, 2); Object o = p1.getPremier(); // OK Number n = p1.getPremier(); // ERREUR non concordance de types : // impossible de convertir de capture-of ? en Number Integer i = p1.getPremier(); // ERREUR non concordance de types : // impossible de convertir de capture-of ? en Integer o = p2.getPremier(); // OK n = p2.getPremier(); // OK i = p2.getPremier(); // ERREUR non concordance de types : // impossible de convertir de capture-of ? extends Number en Integer
Le compilateur désigne par capture-of ? le type inconnu qui paramètre la paire p1, le compilateur désigne par capture-of ? extends Number le type inconnu qui paramètre la paire p2.
8 Généricité et réflexion
On peut savoir si une classe est une classe générique (informations conservées à la compilation), mais on ne peut évidemment pas connaître l'instanciation précise d'un type.
exemple :
public String toString(Class c){ StringBuffer res = new StringBuffer(""); TypeVariable[] tv = c.getTypeParameters(); res.append(c.getName()); if(tv.length!=0){ res.append("<"); for (int i = 0; i < tv.length-1; ++i) res.append(tv[i]+", "); res.append(tv[tv.length-1]); res.append(">"); } return res.toString(); }L'usage de cette méthode pour une classe c permet d'obtenir le nom de la classe suivi de ses paramètres si la classe est générique.
9 Méthodes Pont
Soit les définitions suivantes :
public interface Comparable <T>{ public int compareTo(T t); } public class X implements Comparable<X> { public int compareTo(X t) { return 0; } }
L'effacement dans l'interface Comparable génère une méthode int compareTo(Object t), et la classe X définit la méthode int compareTo(X t). La classe X ne redéfinit donc pas int compareTo(Object t) : le compilateur ajoute la méthode pont :
public class X implements Comparable<X> { public int compareTo(Object t) { return compareTo((X)t); } public int compareTo(X t) { return 0; } }