JAVA : Programmation objet, concepts et pratique

17 juin 2012 rdorigny 0 commentaires


L'objectif de cet article et de traiter de la programmation orientĂ©e objet (POO) le plus simplement possible, mĂȘme si certaines notions sont assez rudes, et en l'illustrant d'exemples simples et concrets.

JAVA est basé sur la POO. D'ailleurs tout programme java est au moins constitué d'une classe. La notion d'objet a été amélioré et on atteint un niveau de complexité comme jamais. En effet de nombreux concepts se sont greffé à la POO tel que l'héritage, le polymorphisme, les métaclasse, les interfaces, les classes abstraites, ... La philosophie objet autour du JAVA a été tellement poussée, que l'on peut dire que tout est objet dans ce langage.

Toutes les fonctionnalités (méthodes) dépendent de la classe racine Object, elle est la "superclass". Le langage JAVA a enrichit progressivement la hiérarchie des classes, et désormais, le nombre de méthodes qui sont offerts par le dernier JDK est époustouflant. Il s'agit probablement du langage de programmation informatique le plus riche qui existe désormais.

1)Notions de classe et d'objet

1.1) Les méthodes et les attributs

La classe agit comme un modÚle pour les objets, elle détermine leurs caractéristiques, on parlera d'attributs. En outre la classe définie les traitements spécifiques à l'objet, on parlera alors de méthodes. Dans le jargon de l'objet, l'instance d'une classe est un objet.

//ModĂšle de classe package p01.classe; public class Classe { String attribut01; String attribut02; String methode01(){ return("methode1"); } String methode02(){ return("methode2"); } }

//Fonction Main pour l'utilisation de la classe package p01.classe; public class TestClasse { public static void main(String[] args) { Classe objet=new Classe(); //Instanciation, création de l'objet objet.attribut01="Attribut01"; objet.attribut02="Attribut02"; System.out.println(objet.attribut01); System.out.println(objet.attribut02); System.out.println(objet.methode01()); System.out.println(objet.methode02()); } }

Si on exécute la fonction main(), cela affiche sur la console:
Attribut01 Attribut02 methode1 methode2

Une autre caractéristique d'un objet est son oid (object identifier), c'est le pointeur (adresse mémoire) qui est sur la structure de l'objet en mémoire.

1.2)Constructeur/Destructeur geter/seter

Dans le programmation objet, il existe un certains nombre de méthodes à définir pour gérer au mieux la vie de l'objet:
  • le constructeur : cette mĂ©thode est appelĂ©e pour crĂ©er l'objet,
  • le destructeur : elle est appelĂ©e pour dĂ©truire l'objet,
  • les getters/setters : autant que cela est possible, il Ă©viter l’accĂšs directe aux attributs de l'objet. Pour cela, on dĂ©finit des mĂ©thodes qui joueront les intermĂ©diaires. Le geter rĂ©cupĂšre les data pour mettre Ă  jour les attributs et seter donne une visibilitĂ© sur la valeur d'un attribut.

  • Pour le constructeur:
    public Classe(String attribut01, String attribut02) { super(); this.attribut01 = attribut01; this.attribut02 = attribut02; }
    La définition d'un constructeur n'est pas obligatoire, un constructeur par défaut existe (sans arguments), mais dés qu'on en a définit un (avec des arguments), il est obligatoire de l'utiliser.
    Donc l'instanciation d'objet sera de la forme :
    //Classe classe1=new Classe(); //Avant Classe classe1=new Classe("Hello world !","coucou"); //AprÚs la redéfinition du constructeur
    La fonction super agit dans le cadre de l'hĂ©ritage (ce n'est pas utile ds notre exemple, mais on peut la mettre pour plutard). Elle appelle le constructeur d'une classe de base. La fonction super doit donc ĂȘtre la premiĂšre instruction d'un constructeur.
    Pour les getters/setters:
    public String getAttribut01() { return attribut01; } public void setAttribut01(String attribut01) { this.attribut01 = attribut01; } public String getAttribut02() { return attribut02; } public void setAttribut02(String attribut02) { this.attribut02 = attribut02; }

    Pour le destructeur:
    public finalize(){ //Destruction des données }
    Dans la pratique, il est rare d'avoir besoin de redéfinir le destructeur. JAVA implémente un ramasse miettes (garbage collector) qui de temps en temps se lance pour récupérer la mémoire utile.

    Sous Eclipse, la génération de ces méthodes peut se faire sur demande par : bouton droit dans la classe, source, Generate...

    1.3)Copie entre objets

    Shallow copy: La Shallow copy (littéralement copie peu profonde)est une des méthodes de copie entre objet, le principe revient à copier les oid entre eux.

    package p08.shallow; public class TestShallow { public static void main(String[] args) { bidon a = new bidon(); bidon b = new bidon(); b.litre=7; System.out.println(a.litre); System.out.println(b.litre); a=b; System.out.println(a.litre); System.out.println(b.litre); } }
    Avec :
    package p08.shallow; public class bidon { int litre=39; }
    Donne :
    39 7 7 7

    Deep copy: La Deep copy (littéralement copie profonde) consiste à recopier les valeurs des attributs entre les objets.

    package p09.deep; public class Brebis { int id=1; protected Brebis clone() throws CloneNotSupportedException { Brebis t = new Brebis(); t.id=this.id; //copy des attributs return t; } }
    Avec :
    package p09.deep; public class TestDeep { public static void main(String[] args) throws Exception { Brebis biquette = new Brebis(); biquette.id=13; System.out.println(biquette.id); Brebis dolly=biquette.clone(); System.out.println(dolly.id); } }
    Donne :
    13 13

    1.4)Objets liés ou embarqués

    Il est possible de mettre des objets en relation entre eux, les chapitres suivants en parleront largement. Néanmoins, sans la notion d'héritage, on peut aussi lier des objets ou de les embarquer (encapsulation de classes ou classes internes selon le jargon).

    Les objets liés:
    public class liant { Lie b; } public class Lie { int c=13; } public class TestLinking { public static void main(String[] args) { liant a = new liant(); Lie b= new Lie(); a.b=b; System.out.println(a.b.c); //Affiche 13 } }


    Les objets embarqués:
    public class Contenant { Contenu b=new Contenu(); class Contenu { int c=27; } } public class testcomposition { public static void main(String[] args) { Contenant a = new Contenant(); System.out.println(a.b.c); //Affiche 27 } }

    2)HĂ©ritage et polymorphisme

    2.1)Notions sur l'héritage

    Le concept de la modélisation objet/classe est de factoriser le code au maximum. Notamment, un des objectifs est de réutiliser le code à partir de la classe modÚle. On définit des classes hiérarchiques. La classe racine est aussi nommée super-class. Prenons l'exemple d'un cercle et d'un carré, nous définissons alors leurs attributs et méthodes comme le calcul de surface de l'objet.
    public class Cercle{ int x; int y; int rayon; double surface() { return rayon * rayon * Math.PI; } } public class Rectangle { int longueur; int largeur; int x; int y; double surface() { return longueur * largeur; } } public class Test { public static void main(String[] args) { Rectangle c; Cercle r; c=new Rectangle(); r=new Cercle(); c.largeur=100; c.longueur=300; c.x=100; c.y=100; r.rayon=100; r.x=500; r.y=400; System.out.println(c.surface()); System.out.println(r.surface()); } }

    Si on reprend l'exemple, nous pouvons définir une classe forme qui reprend les éléments communs aux classes Cercle et Rectangle. Ensuite, il suffit de les faire hériter à cette super-class, les classes dérivés devront définir les particularités de la classes par des attributs ou méthodes spécifiques.
    public class Forme { int x; int y; } public class Cercle extends Forme{ int rayon; double surface() { return rayon * rayon * Math.PI; } } public class Rectangle extends Forme { int longueur; int largeur; double surface() { return longueur * largeur; } } public class test { public static void main(String[] args) { Rectangle c; Cercle r; c=new Rectangle(); r=new Cercle(); c.largeur=100; c.longueur=300; c.x=100; c.y=100; r.rayon=100; r.x=500; r.y=400; System.out.println(c.surface()); System.out.println(r.surface()); } }

    On notera le mot clé extends qui permet de préciser la classe supérieure. En outre, en JAVA une classe ne peut hériter que d'une seule classe.

    2.2)Gestion des accĂšs entre classes

    L'hĂ©ritage permet l’accĂšs Ă  des attributs ou des mĂ©thodes. Il possible de restreindre ces accĂšs, quatre niveaux autorisent l'utilisation de tel ou tel mĂ©thode. Il y a :
  • public : la mĂ©thode ou l'attribut est accessible par tout le monde,
  • private : la mĂ©thode ou l'attribut ne seront pas hĂ©ritĂ©s, il reste au niveau de la classe/objet,
  • protected : seul les classes qui hĂ©ritent, accĂ©deront aux attributs/mĂ©thodes,
  • friendly : l'accĂ©s est complet Ă  l'intĂ©rieur du package, c'est ce niveau qui est mis par dĂ©faut si rien n'est stipulĂ©.
  • public class Clovis extends Moule { } public class Moule { private int e; protected int d; public int c; int y; //friendly int yplus(int a, int b) { return a+b; } private int eplus(int a, int b) { return a+b; } protected int dplus(int a, int b) { return a+b; } public int cplus(int a, int b) { return a+b; } } public class Test { public static void main(String[] args) { Moule m=new Moule(); m.c=1; m.d=2; m.y=3; System.out.println(m.c); System.out.println(m.d); System.out.println(m.y); System.out.println(m.cplus(1, 1)); System.out.println(m.dplus(1, 1)); System.out.println(m.yplus(1, 1)); Clovis c= new Clovis(); c.c=1; c.d=2; c.y=3; System.out.println(c.c); System.out.println(c.d); System.out.println(c.y); System.out.println(c.cplus(1, 1)); System.out.println(c.dplus(1, 1)); System.out.println(c.yplus(1, 1)); } }
    Donnera:
    1 2 3 2 2 2 1 2 3 2 2 2

    Il n'est pas rare que les attributs de la super-class soient private et que les getters/setters soient public ou protected. On parle alors d'encapsulation, car les accÚs aux attributs sont limités aux méthodes, ce qui permet des traitements complémentaires en cas de nécessité pour éviter que des valeurs erronées soient affectées.

    Si un constructeur d'une classe dĂ©rivĂ©e appelle le constructeur de base, cela se fait par la commande super qui doit ĂȘtre appelĂ©e en premier. Attention, la commande ne concerne que le constructeur immĂ©diatement supĂ©rieur. Il est possible d'appeler une mĂ©thode de la classe supĂ©rieure par super.nom_method(val1,val2);

    On définit le mot clé static, il s'agit d'un attribut ou d'une méthode commune à la classe qui n'est pas liée à un instance particuliÚre. On peut donc définir une variable ou une méthode.
    class Cercle { public static rayon; ... } Cercle c1=new Cercle(); Cercle c2=new Cercle();

    Dans l'exemple ci-dessus, les variable c1.rayon et c2.rayon sont les mĂȘmes variables. Pour Ă©viter les confusions, on note Cercle.rayon cette variable, cela permet de dire qu'elle est rattachĂ©e Ă  la classe et non Ă  l'instance.
    De la mĂȘme façon, une mĂ©thode static f est notĂ©e Cercle.f() .

    Il ne faut pas confondre les mots clés final et static.

    Gestion des variables dans un programme JAVA:
    JAVA définit plusieurs types de variables :
  • locale : est dĂ©finit dans un bloc,
  • globale : est dĂ©finie dans le programme principal main(),
  • de classe : est dĂ©finie dans la classe, et commune, donc public static,
  • d'instance : ce sont les attributs.

  • Il existe deux façon de transmettre une variable:
  • par valeur : on transmet une copie du contenu de la variable,
  • locale : on transmet l'adresse de la variable (qui peut ĂȘtre un objet).
  • A la sortie de la mĂ©thode, la variable transmise par valeur sera inchangĂ©e.
    Les échanges avec les méthodes sont réalisés de deux façons, les variables de types primitifs sont transmis par valeur, les échanges d'objet sont transmis par variable.

    2.3)Redéfinition et sur-définition

    Il ne faut pas la confondre avec la sur-dĂ©finition qui consiste Ă  crĂ©er des mĂ©thodes de mĂȘmes noms mais avec des attributs diffĂ©rents. La re-dĂ©finition consiste Ă  redĂ©finir une mĂ©thode qui a Ă©tĂ© hĂ©ritĂ©.

    Prenons un exemple, imaginons une super-class qui définit la méthode imprime(), qui affiche Hello. Avec la redéfinition, il est possible de refaire dans une méthode imprime() qui affiche Hello world! dans une classe qui hérite de la super-class. On enrichit la méthode.
    public void imprime() { super.imprime(); System.out.println(" world!"); }

    JAVA autorise de sur-définir une méthode qui a été héritée.
    class Haute { public fct1(int n){.....} public fct2(float x){.....} ... } class Basse extends Haute { public fct1(int n){.....} //Redéfinition public fct2(double x){.....} //Sur-définition ... }

    Le mot clĂ© final permet de dĂ©finir une variable comme constante. On peut Ă©galement l'appliquer Ă  une mĂ©thode ou Ă  une classe. Une mĂ©thode qui estampillĂ©e final ne peut pas ĂȘtre redĂ©finie dans les classes dĂ©rivĂ©es. De plus, une classe dite final ne peut pas ĂȘtre dĂ©rivĂ©e.

    Un exemple de surdéfinition - surcharge :
    public class Testsurcharge { public static void main(String[] args) { Calcul calcul = new Calcul(); System.out.println(calcul.plus(1, 1)); System.out.println(calcul.plus(1, 1, 1)); System.out.println(calcul.plus(1, "1")); System.out.println(calcul.plus(1, 1, 1, 1)); } } public class Calcul { public int plus (int a, int b){ return a+b; } public int plus (int a, int b, int c){ return a+b+c; } public int plus (int a, String b){ return a; } public int plus (int ... a ){ return 1; } }

    Donne Ă  l’exĂ©cution:
    2 3 1 1

    Un exemple de redéfinition :
    public class Arithmetique { public int plus (int a, int b){ return a+b; } } public class sousArithmetique extends Arithmetique { public int plus (int a, int b){ int somme=0; somme=super.plus(a,b); return somme+1; } } public class TestRedefinition { public static void main(String[] args) { Arithmetique a = new Arithmetique(); sousArithmetique s = new sousArithmetique(); System.out.println(a.plus(3, 3)); System.out.println(s.plus(3, 3)); } }

    Donne Ă  l’exĂ©cution:
    6 7

    2.4)Le polymorphisme

    Le mot polymorphisme est formĂ© Ă  partir du grec ancien πολλοί (polloĂ­) qui signifie « plusieurs » et μορφος (morphos) qui signifie « forme ». En POO, le polymorphisme est liĂ© Ă  l'hĂ©ritage, il permet de manipuler des objets sans en connaitre le type. Ce mĂ©canisme sera en mesure de retrouver la mĂ©thode relative aux types traitĂ©s.
    Prenons l'exemple suivant:
    public abstract class Forme { int x; int y; abstract double surface(); } public class Cercle extends Forme{ int rayon; double surface() { return rayon * rayon * Math.PI; } } public class Rectangle extends Forme { int longueur; int largeur; double surface() { return longueur * largeur; } } public static void main(String[] args) { Rectangle c; Cercle r; c=new Rectangle(); r=new Cercle(); c.largeur=100; c.longueur=300; c.x=100; c.y=100; r.rayon=100; r.x=500; r.y=400; Forme f[]={r,c,r}; for (int i=0;i<f.length;i++){ System.out.println(f[i].surface()); } } }

    Dans l'exemple ci-dessus la notion de polymorphisme est au niveau de l'affichage de la surface. En effet, le compilateur a su en fonction du type d'objet appeler la bonne méthode. Autre point à noter, l'affectation d'un objet Rectangle ou Cercle à un type Forme a été acceptée. On peut donc affecter à un objet, un objet de type ascendant.

    Le polymorphisme permet un comportement adapté selon le type d'objet, c'est le compilateur qui traite la ligature de la bonne méthode. Il est donc possible de traiter avec une super-class sans connaitre ou faire connaitre les classes qui sont héritées! Le développeur ne se soucie plus du type de l'objet (du moins pas complÚtement) et peu masquer des sous-classes s'il le souhaite puisqu'avec ce mécanisme, le compilateur sera en mesure de retrouver la bonne méthode.

    2.5)La super-class Object

    JAVA a définit une super-class appelée Object, elle est la classe racine. Toutes les classes héritent par de cette classe. On pourrait écrire :
    public class Forme extends Object{ int x; int y; }

    Il est possible de créer un objet de la classe Object, toutefois pour pour utiliser une méthode, il faudra passer par une conversion adaptée.
    Cercle c = new Cercle(); Object obj; o=c; System.out.println(o.surface()); //erreur Ă  la compil System.out.println(((Cercle) obj).surface()); //OK Cercle c2 = (Cercle) obj; System.out.println(obj.surface());

    La classe Object a quelques méthodes qui sont héritées par les objets dérivés, on peut les redéfinir ou les utiliser directement:
  • toString : Affiche une chaĂźne de caractĂšre relative Ă  l'objet,
  • equals : Compare les adresses de deux objets et renvoie un boolĂ©en,

  • Ensuite et pour que la classe soit complĂšte, il faudra dĂ©finir les getters/setters, une mĂ©thode clone, une mĂ©thode hashCode et finalize.

    2.6)Les classes abstraites

    Une classe abstraite ne peut pas ĂȘtre instanciĂ©e, il n'y a pas d'objet lui correspondant. Elle sert simplement de modĂšle aux classes dĂ©rivĂ©es, et donc de dĂ©finir des attributs et mĂ©thodes gĂ©nĂ©riques. A partir du moment oĂč une mĂ©thode est abstraite, la classe est abstraite et doit ĂȘtre dĂ©finie comme tel.

    En fait une classe abstraite permet de dĂ©finir/imposer des Ă©lĂ©ments dans les classes dĂ©rivĂ©es par l'intermĂ©diaire de l'hĂ©ritage et du polymorphisme. Souvent les mĂ©thodes sont simplement dĂ©finies mais pas dĂ©crites, elles le seront dans les classes infĂ©rieures. Une classe abstraite doit ĂȘtre dĂ©clarĂ©e comme public, puisque son objectif est d'ĂȘtre redĂ©finie.

    Dans l'exemple que nous avons pris pour décrire le polymorphisme, la classe Forme est abstraite. La méthode surface est définie mais non décrite, elle est décrite dans les classes dérivées Rectangle et Cercle. Dans ce cas:
    Forme f; // Pas de problÚme ici, on créé une référence f=new Forme(); //Erreur ici, classe abstraite donc pas d'instanciation

    Un autre exemple:
    public abstract class Telecommande { abstract void on(); abstract void off(); abstract void pgplus(); abstract void pgmoins(); } public class Samsung extends Telecommande { @Override void on() { System.out.println("Samsung on"); } @Override void off() { System.out.println("Samsung off"); } @Override void pgplus() { System.out.println("Samsung +"); } @Override void pgmoins() { System.out.println("Samsung -"); } } public class Sony extends Telecommande { @Override void on() { System.out.println("Sony on"); } @Override void off() { System.out.println("Sony off"); } @Override void pgplus() { System.out.println("Sony +"); } @Override void pgmoins() { System.out.println("Sony -"); } } public class TestAsbstraction { public static void main(String[] args) { Sony xhf33=new Sony(); xhf33.on(); xhf33.pgplus(); xhf33.pgplus(); xhf33.pgplus(); xhf33.pgplus(); xhf33.off(); Samsung gt9000=new Samsung(); gt9000.on(); gt9000.off(); } }

    2.7)Les interfaces

    Une interface est une classe qui ne possÚde que des méthodes abstraites.

    Les interfaces ont des fonctionnalités complémentaires aux classes abstraites, ce qui justifient leurs existences et le fait qu'on les retrouvent un peu partout.
    public interface interf1 { public abstract void f(int n); //DĂ©claration entĂȘte, la dĂ©claration public abstract est facultative double g(float x); } public interface interf2 { void h(int n); double i(float x); } class test implements interf1, interf2 { // Il faudra donc dĂ©finir les mĂ©thodes f, g, h et i. ... }

    Une interface ne dĂ©finie que des mĂ©thodes abstraites, et mĂȘme que des entĂȘtes de mĂ©thodes. Une interface peut dĂ©finir des constantes par static final int K=8;

    Voici un exemple d'utilisation:
    public interface Telecommande { abstract void on(); abstract void off(); abstract void pgplus(); abstract void pgmoins(); } public class Samsung implements Telecommande { @Override public void on() { System.out.println("Samsung on"); } @Override public void off() { System.out.println("Samsung off"); } @Override public void pgplus() { System.out.println("Samsung +"); } @Override public void pgmoins() { System.out.println("Samsung -"); } } public class Sony implements Telecommande { @Override public void on() { System.out.println("Sony on"); } @Override public void off() { System.out.println("Sony off"); } @Override public void pgplus() { System.out.println("Sony +"); } @Override public void pgmoins() { System.out.println("Sony -"); } } public class TestInterfaces { public static void main(String[] args) { Sony xhf33=new Sony(); xhf33.on(); xhf33.pgplus(); xhf33.pgplus(); xhf33.pgplus(); xhf33.pgplus(); xhf33.off(); Samsung gt9000=new Samsung(); gt9000.on(); gt9000.off(); } }

    Une classe peut implĂ©menter plusieurs interfaces, c'est un des gros intĂ©rĂȘts des interfaces. Une interface n'est pas liĂ©e Ă  un hĂ©ritage, mais il est possible d'appliquer une interface Ă  des classe dĂ©rivĂ©es.
    public interf { void f(int n); double g(float x); } class Sup {...} class Inf extends Sup implements interf{ //les méthodes f et g seront définies dans Sup ou dans Inf ... }

    2.8)Les classes d'encapsulation

    JAVA définit les types dits primitifs (integer, float, ...). JAVA a redéfinit ces types sous formes d'objets, ce sont les wrappers ou classes enveloppes. L'objectif est de disposer de méthodes autour de ces variables qui seront désormais des objets.

    2.9)Les classes anonymes

    Il s'agit d'une classe qui ne possĂšde pas de nom.
    Cercle c; c=new Cercle() { //Ici on définit ce que fait le constructeur };
    En fait tout se passe comme si nous avions Ă©crit:
    Cercle c; class Cercle2 extends Cercle { //Ici on définit ce que fait le constructeur }; c=new Cercle2() ;

    Les classes anonymes sont Ă  Ă©viter, car cela complexifie le code pour un intĂ©rĂȘt faible.

    2.10)Notions de métaclasse

    Une métaclasse est une classe dont les instances sont des classes.Java ne permet pas vraiment la notion métaclasse au sens littéral. En revanche, il propose des méthodes permettant l'accÚs aux informations relatives à une classe.

    import java.lang.reflect.Field; import java.lang.reflect.Method; public class TestMetaclasse { public static void main(String[] args) throws Exception { Class classe = Class.forName("java.lang.Integer"); //Class classe = Class.forName("p16.metaclasse.TestMetaclasse"); System.out.println(classe.getCanonicalName()); System.out.println(classe.getName()); System.out.println(classe.getSimpleName()); System.out.println(classe.getName()); Method[] methodes = classe.getDeclaredMethods(); for (int i = 0;i<methodes.length;i++) System.out.println(methodes[i].getName()); Field[] fields = classe.getDeclaredFields(); for (int i = 0;i<fields.length;i++) System.out.println(fields[i].getName()); } }

    Conclusion

    L'objectif de la POO est de factoriser le code. C'est aussi une nouvelle façon de voir la programmation en opposition Ă  la programmation structurĂ©e. Nous l'avons vu, ce type de code est riche de nombreuses fonctionnalitĂ©s ou mĂ©canismes qui sont parfois complexes. Attention, la factorisation rend souvent le code indigeste et difficile Ă  reprendre. Heureusement, JAVA a rendu les mĂ©canismes le plus simple possible pour pallier Ă  cette complexitĂ©, et les outils eclipse ou netbeans ont largement favorisĂ© son succĂšs. La suppression des pointeurs et le fait d'ĂȘtre plus ou moins open-source n'y sont pas Ă©tranger Ă©galement.






    Pseudonyme (obligatoire) :
    Adresse mail (obligatoire) :
    Site web :




    © 2023 www.doritique.fr par Robert DORIGNY