| 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 OK
La dernière ligne provoque l'erreur de compilation suivante : Impossible d'effectuer une vérification instanceof sur le type paramétré Pairepublic 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;
}
}