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 :

    Pour valider votre commentaire, écrivez le texte affiché sur l'image :



    © 2017 www.doritique.fr par Robert DORIGNY