La généricité
Transcription
La généricité
La généricité 13 avril 2016 1 Un exemple connu La classe ArrayList permet de représenter un tableau sous forme d’un objet et de bénéficier ainsi de nombreuses méthodes telles que l’ajout ou le retrait d’éléments. Lorsqu’on veut utiliser cette classe, il faut préciser le type des éléments que l’on place dans le tableau. Par exemple, si l’on veut un ArrayList contenant des String, il faudra déclarer la variable et créer l’objet ArrayList comme suit. ArrayList <String > var ; v a r = new A r r a y L i s t < S t r i n g > ( ) ; Si l’on va voir la documentation de la classe ArrayList, on voit entre autres les choses suivantes : – Class ArrayList<E> – boolean add(E e) Dans le nom de la classe apparaît un E qui ne correspond pas à un type java, et ce E apparaît également comme type de paramètres ou de résultat de méthodes de la classe. Ce E est un nom qui peut être remplacé par n’importe quel type lorsqu’on crée effectivement un objet. Lorsque cette classe est définie (et c’est ce qui est décrit dans la documentation), le E n’a pas de valeur précise. C’est à la création de l’objet, lors de l’instanciation de la classe, que ce E est remplacé par le nom d’une classe bien précise (String dans notre cas). On appelle généricité cette possibilité de retarder la définition d’un type utilisé dans une classe. 2 Ecriture d’une classe générique Si nous voulons écrire nous-même une classe générique, il faut reprendre à peu près la même chose que ce que l’on voit dans la documentation de la classe ArrayList : il faut donner des noms provisoires aux types dans le nom de la classe, avec les signes inférieur et supérieur. Après cela, les nom provisoires peuvent être utilisés pour déclarer des variables, des paramètres, des types de résultat. Et là, il ne faut pas mettre d’inférieur et supérieur. Nous allons prendre l’exemple d’une mini-classe ArrayList faite maison, c’est à dire une classe encapsulant un tableau et offrant quelques méthodes : add, set, get, remove et size. p u b l i c c l a s s M y A r r a y L i s t <UnType >{ p r i v a t e UnType [ ] t a b ; p r i v a t e i n t nb = 0 ; MyArrayList ( ) { / / On v o u d r a i t é c r i r e i c i t a b =new UnType [ 1 0 0 ] 1 / / mais ça n ’ e s t pas permis par l e l a n g a g e t a b = ( UnType [ ] ) new O b j e c t [ 1 0 0 ] ; } p u b l i c v o i d add ( UnType e ) { t a b [ nb ] = e ; nb ++; } p u b l i c UnType s e t ( i n t i , UnType e ) { i f ( i <0 | | i >=nb ) t h r o w new I n d e x O u t O f B o u n d s E x c e p t i o n ( ) ; UnType r e s = t a b [ i ] ; t a b [ i ]= e ; return res ; } p u b l i c UnType g e t ( i n t i ) { return tab [ i ] ; } p u b l i c UnType remove ( i n t i ) { i f ( i <0 | | i >=nb ) t h r o w new I n d e x O u t O f B o u n d s E x c e p t i o n ( ) ; UnType r e s = t a b [ i ] ; f o r ( i n t j = i ; j <nb −1; j ++) t a b [ j ]= t a b [ j + 1 ] ; nb−−; return res ; } public int size (){ r e t u r n nb ; } } Ensuite, on peut utiliser la classe générique en remplaçant, à chaque objet créé, UnType par le nom d’une classe Java. public class Utilise { p u b l i c s t a t i c v o i d main ( S t r i n g [ ] a r g s ) { M y A r r a y L i s t < S t r i n g > mal ; mal = new M y A r r a y L i s t < S t r i n g > ( ) ; M y A r r a y L i s t <Chose > mal2=new M y A r r a y L i s t <Chose > ( ) ; mal . add ( " un " ) ; mal . add ( " deux " ) ; System . o u t . p r i n t l n ( mal . s i z e ( ) ) ; System . o u t . p r i n t l n ( mal . remove ( 1 ) ) ; Chose c p t = new Chose ( " r i r i " ) ; mal2 . add ( c p t ) ; mal2 . add ( new Chose ( " f i f i " ) ) ; System . o u t . p r i n t l n ( mal2 . g e t ( 0 ) . nom ) ; } 2 } c l a s s Chose { S t r i n g nom ; Chose ( S t r i n g n ) { nom=n ; } p u b l i c S t r i n g getNom ( ) { r e t u r n nom ; } } 3 Note à propos des types génériques Lors de l’instanciation d’une classe générique, on peut remplacer les noms de types par n’importe quel type référence, c’est à dire un nom de classe, un type tableau, un nom d’interface, mais pas un type primitif (int, double, boolean, char). Si l’on essaye par exemple cette instanciation : MyArrayList<int> mal4 = new MyArrayList<int>(); le compilateur refuse le programme avec l’erreur : Utilise.java:16: error: unexpected type MyArrayList<int> mal4 = new MyArrayList<int>(); ^ required: reference found: int Il existe quatre classes prédéfinies permettant de représenter avec des objets des entiers, nombres à virgule, booléens et caractères : ce sont les classes Integer, Double, Boolean et Character. On peut par exemple écrire quelque chose comme : MyArrayList<Integer> mal5 = new MyArrayList<Integer>(); mal5.add(7); mal5.add(12); int x = mal5.get(0); System.out.println(x); 4 Généricité et interface Nous avons vu l’utilisation de généricité pour les classes. La généricité est aussi utilisée avec les interfaces d’une façon un peu différente. En effet, les interfaces ne sont pas instanciées, ce n’est donc pas à la création d’objet que les types inconnus sont remplacés par des types prévis. C’est lors de l’implémentation de l’interface. Dans la plupart des cas, on utilise le type inconnu pour représenter le type de la classe qui implémente l’interface, car on a besoin d’utiliser ce type pour les paramètres et/ou résultat des méthodes de l’interface. Voyons l’exemple de l’interface prédéfinie Comparable de Java. Cette interface peut s’écrire comme suit : 3 p u b l i c i n t e r f a c e Comparable <T>{ i n t compareTo ( T o ) ; } Cette interface décrit la propriété d’avoir un ordre sur un type. La méthode compareTo doit comparer l’objet sur laquelle elle est appelée avec l’objet passée en paramètre et l’on veut généralement comparer des objets de même type. Un appel à la méthode s’écrira comme suit : objet1.compareTo(objet 2). Cette interface est implémentée par la classe prédéfinie String (la classe des chaînes de caractère) avec comme valeur de T le type String lui-même. Dans la documentation de String on voit : public class String implements Comparable<String>... On voit quelque chose d’analogue avec d’autres classes prédéfinies de Java : – public class Integer implements Comparable<Integer> – public class Date implements Comparable<Date> – ... Le plus souvent, la généricité est ainsi employée dans les interfaces pour donner un nom au type de la classe qui l’implémente. 5 Exemple d’interface générique Nous proposons l’interface Affiliable qui propose de représenter des notions de parenté entre objets du même type. Les méthodes fournies permettent de déclarer un lien de filiation et de tester les liens entre deux objets. On pourra utiliser cette interface avec des êtres humains, des animaux, des programmes objets (héritage), mais on ne pourra pas créer de lien de filiation entre un homme et un chien ou entre une chouette et un programme : la filiation se fera uniquement entre objets de la même classe et pour cela, on a besoin de la généricité. p u b l i c i n t e r f a c e F i l i a t i o n <T>{ T[] getFils (); void d e c l a r e F i l s (T f i l s ) ; boolean e s t F i l s (T t ) ; boolean estPere (T t ) ; boolean estAncetre (T t ) ; } import java . u t i l . ArrayList ; p u b l i c c l a s s Humain i m p l e m e n t s F i l i a t i o n <Humain >{ S t r i n g nom ; Humain p e r e ; A r r a y L i s t <Humain > f i l s ; p u b l i c Humain ( S t r i n g n ) { nom = n ; f i l s =new A r r a y L i s t <Humain > ( ) ; } p u b l i c Humain [ ] g e t F i l s ( ) { r e t u r n f i l s . t o A r r a y ( new Humain [ 0 ] ) ; } p u b l i c v o i d d e c l a r e F i l s ( Humain t ) { 4 i f ( t . p e r e != n u l l ) t . p e r e . f i l s . remove ( f i l s ) ; t . pere = t h i s ; t h i s . f i l s . add ( t ) ; } p u b l i c b o o l e a n e s t F i l s ( Humain t ) { return t . pere . equals ( t h i s ) ; } p u b l i c b o o l e a n e s t P e r e ( Humain t ) { return t h i s . pere . equals ( t ) ; } p u b l i c b o o l e a n e s t A n c e t r e ( Humain t ) { Humain a n c e t r e = t . p e r e ; w h i l e ( a n c e t r e ! = n u l l && a n c e t r e ! = t h i s ) ancetre = ancetre . pere ; r e t u r n ( a n c e t r e != n u l l ) ; } } 5