Java Coding suite

On reprend les classes, la classe animal :

package p1;
public class animal{
protected int age;
int poids;
String nom;
}

La classe kangourou :

package p1;
public class kangourou extends animal{
private int longueur_de_saut;
}

Et le programme en lui même :

import p1.*;
public class prog{
animal inconu;
 	prog()
{
try{
inconu = new animal();
inconu.age=5;
inconu.poids=25;
}
catch(Exception ex){System.out.println(ex);}
}
 	public static void main(String []args)
{
prog p=new prog();
}
}

Compilation :

prog.java:13: age has protected access in p1.animal
inconu.age=5;
^
prog.java:14: poids is not public in p1.animal; cannot be accessed from outside package
inconu.poids=25;
^
2 errors

Erreurs explicites non ? La première du à l'accès en protected et l'autre du à l'accès en friendly.
La solution la plus couremment utilisée en Java consiste à rendre les attributs inaccessibles pour la plupart des classes (pas en public) et à mettre des méthodes public qui accèdent aux attributs à notre place.
Par exemple nos classes devraient ressembler à ça :

package p1;
public class animal{
protected int age;
int poids;
public void setPoids(int x){poids=x;}
String nom;
}
-------------------------
package p1;
public class kangourou extends animal{
private int longueur_de_saut;
public void setAge(int x){age=x;}
}

-------------------------
import p1.*;
public class prog{
kangourou Kangoo;
animal inconu; prog()
{
try{
System.out.println("dans le constructeur");
Kangoo = new kangourou();
inconu = new animal();
Kangoo.setAge(12);
inconu.setPoids(25);
Kangoo.setPoids(40);
}
catch(Exception ex){System.out.println(ex);}
}
public static void main(String []args)
{
System.out.println("Entry Point");
prog p=new prog();
}
}
----------------------------

Comme ça, malgréque l'age soit protected et le poids soit en friendly, on accède sans problème à ces attributs par les méthodes setPoids() et setAge().
Les méthodes ne commencent pas obligatoirement par set ou get, c'est juste une habitude (une convention koi).
Les méthodes comme setAge() sont appelées modificateurs et les getAge() sont appelées accesseurs.
Le fait de cacher les attributs et de ne montrer que les méthodes n'est pas une obligation mais tout le monde l'utilise (une méga convention en quelque sorte).

J'avais oublié des notions importantes, les voici.
Toutes les classes que l'on utilise, que ce soient celles incluses dans le JDK ou celles que l'on déclare, héritent de la classe de base, la classe Objet (java.lang.Objet). Des fois on entend parler de 'superclasse' pour désigner la classe mère (ou classe ancètre). Si vous avez bien suivi vous avez déviné que la classe Objet est la superclasse de toutes les classes.
Cette classe est importante car elle possède quelques méthodes très utiles, méthodes qui seront utilisables par n'importe quelle classe.
Parmis ces méthodes il y a :
Objet clone()
La méthode clone() renvoie une copie de l'objet en cours. Il faut l'utiliser car une simple affectation ne marche pas. Si on fait :
kangourou k1=new kangourou();
kangourou k2=new kangourou();
k1=k2;
cela ne vas pas donner au kangourou k1 les propriétés du kangourou k2. Cela va faire pointer k1 vers k2 (au niveau des adresses mémoires). Si ce concept ne vous dit rien, je vous conseille d'étudier un peu l'assembleur ou les pointeurs en C. Si on veut avoir sa fonction de recopie on peut déclarer par exemple pour la classe kangourou :


public static void recopie(kangourou k1, kangourou k2)
{
  k1.age=k2.age;
k1.poids=k2.poids; k1.nom=k2.nom; }

Bref le mot clé static est très utile :) Pour revenir à notre méthode clone(), on peut faire :
k1=k2.clone();

Il y a aussi la fonction boolean equals(Objet obj) qui permet de comparer deux objets sur leurs contenus (car pour les même raisons (k1==k2) ne fait un test que sur les adresses mémoires).
Pour faire une vraie comparaison on va faire :
if (k1.equals(k2)){...

Une méthode TRÈS utilisée est la méthode toString(), elle renvoie une String qui contient la valeur de l'objet. C'est souvent cette fonction qui nous libère des problèmes de conversion.
Il reste d'autres méthodes dans la classe Object mais je ne les présente pas.
Vous avez peut-être remarqué que Java respecte les conventions avec les classes qu'il fournit :)

Une méthode utile est la méthode System.gc() qui appelle le garbage collector (ramasse miette en Français). Ne l'utilisez pas comme un maniaque cela ne servirait à rien. Le garbage collector est automatiquement lancé en tant que thread (une tache de fond en quelque sorte).Cest lui qui fait le ménage dans la mémoire. Il peut être utile de l'appeler dans des programmes qui font des énormes calculs de façon continue car dans ce cas les calculs ne laissent pas au garbage collector le temps d'intervenir. A utiliser que dans les brute forceur de mot de passe :)

-- je fais une pause ici. demain constructeurs, surcharge, tableaux puis on étudiera un programme sympa pour nous :) --

Le polymorphisme
Le polymorphisme est une notion liée à l'héritage. Le polymorphisme fait que lorsque l'on instance un objet, celui-ci est vu comme une instance de sa classe et de ses sous classes.
Par exemple quand on déclare un kangourou, on déclare en même temps un animal et un Object (superclasse). Le polymorphisme permet des affectations telles que :
animal a=new kangourou();
ou encore
animal a=new animal();
kangourou k=new kangourou();
a=k;

Evidemment cela ne marche pas dans l'autre sens (kangourou k=new animal()) car un kangourou possède des caractèristiques supplémentaires que la classe animal ne possède pas.

La redéfinition
Il peut arriver que l'on ai besoin de redéfinir une méthode. Qu'est-ce que cela veut dire ? Cela veut dire que par exemple on a une classe A :

class A
{
public A()
{
System.out.println("Dans le constructeur de la classe A");
}
  public void oukoné()
  {
    System.out.println("On est dans la classe A");
  }
}

Ensuite on crée une classe B, fille de A dans laquelle on redéfinit la méthode oukoné :

class B extends A
{
public B()
{
System.out.println("Dans le constructeur de la classe B");
} public void oukoné()
{
//super.oukoné();
System.out.println("On est dans la classe B");
}
}

Evidemment on fait notre programme principal à part :

class Prog
{
A a;
B b; Prog()
{
System.out.println("+ new A");
a=new A();
System.out.println("+ new B");
b=new B();
} public void run()
{
System.out.println("+ a.oukoné()");
a.oukoné();
System.out.println("+ b.oukoné()");
b.oukoné();
} public static void main(String args[])
{
Prog p=new Prog();
p.run();
}
}

Vous avez là une façon de programmer en Java on ne peut plus classique. Tout d'abord on déclare les attributs de la classe. Ensuite dans le constructeur on s'occupe des allocations et enfin on appelle une méthode run() qui se charge des instructions principales du programme. Voyons ce qu'il se passe losrque nous lançons le programme :

+ new A
Dans le constructeur de la classe A
+ new B
Dans le constructeur de la classe A
Dans le constructeur de la classe B
+ a.oukoné()
On est dans la classe A
+ b.oukoné()
On est dans la classe B

D'abord la méthode que l'on appelle le constructeur est celle qui est lançée lorsque on utilise la new(); Le constructeur n'a pas de type et porte exactement le même nom que la classe. Si on ne déclare pas de constructeur pour une classe, c'est le constructeur de la classe Object qui est appelé. Ce dernier se charge de faire une allocation mémoire tout ce qu'il y a de plus typique.
Mais étudions l'output du programme. La première ligne (comme toutes les lignes que j'ai précédé du signe +) permet de savoir où on est dans le programme (affichage sur la sortie standard). La seconde ligne correspond à l'instruction a=new A();
Ensuite on passe dans le constructeur de A puis de B. Cela correspond à l'instruction b=new B();
Le constructeur de la classe dérivée appelle automatiquement le constructeur de la classe mère. Il y a comme un système de poupées russes. Cela est tout à fait normal si on veut que l'objet soit convenablement crée.

Passons à ce qui suit. On appelle la méthode a.oukoné() qui nous dit que l'on est dans la classe A. Quand on appelle le b.oukoné() on remarque que contrairement à précédemment on ne passe pas par la classe A. En effet le fait de remonter les classes ne marche qu'avec les constructeurs.
On remarque aussi que A et B ont une méthode du même nom. On dit que l'on a 'redéfini' la méthode oukoné().
Java marche de la façon suivante : si on appelle la méthode oukoné() de l'objet b, Java va regarder si cet objet possède effectivement une méthode oukoné(). Si c'est le cas, il l'exécute et s'arrête sinon il remonte d'une classe et recommence à chercher...

Maintenant imaginons que l'on veuille que notre fonction marche de la même façon que les constructeurs. Pour cela on utilise le mot clé super. super désigne la classe mère. On enlève donc le '//', qui sert de commentaire, dans la classe B afin que l'instruction super.oukoné() soit effectué. On a alors :

+ b.oukoné()
On est dans la classe A
On est dans la classe B

Si vous faites appel au super, vous devez le faire en premier lieu, sans quoi la compilation ne marchera pas.

Les tableaux
Ben finalement vous y avez droit (bande de ptits veinards :)
Si on veut déclarer un tableau de 10 entiers on écrit :
int [] tab=new int [10];
L'utilisation de new est obligatoire. int tab[10] ne marchera pas.
Pour savoir la taille d'un tableau il y a l'attribut length. Par exemple tab.length vaut 10.
Tableau avec des valeurs pédéfinies : int tab[] = {1,2,3,4,5,6}
Une matrice : int atb [][] = new int [4][]; On est obligé de fournir la taille de la première dimension.
int [] x, y[]; correspond à int x[]; et int y[][];

La surcharge
Cela consiste à mettre dans une même classe deux fois ou plus la même méthode mais avec des profil différents.
A quoi ça sert ? Et bien si on fait une classe Point et qu'on veut avoir plusieurs façon de déclarer un point, par exemple :
Point p1=new Point();
Point p2=new Point(x, y);
Point p3=new Point(p2);

Dans ce cas il faudra déclarer les fonctions :
Point()
Point(int a,int b)
Point(Point p)

La encore c'est un concept super pratique car on est pas obligé de retenir quarante noms de fonctions...

Petit ajout que j'avais oublié lors de l'écriture : les exceptions
La classe Exception permet de gérer les éventuelles erreurs et possède des méthodes permettant de savoir explicitement quelle erreur a été provoqué.
Il n'est pas nécessaire de mettre des exceptions partout : quand cela est nécessaire le compilateur Java le dit de façon très compréhensible.
Une fois un système d'exception inclus dans notre programme et si une erreur se produit dans la section de notre exception, on dit que une exception a été 'levée'.
Au niveau programmation Java, une exception est un objet qui dialogue avec la JVM (machine virtuelle Java pour les distraits).
Il y a un tas de types d'exceptions, toutes héritent de la super classe Exception.
Il y a deux façons de gérer les exceptions :

- avec les try, catch, et finally

try
{
    le_code_qui_peut_éventuellement_lever_une_exception
}
catch(le_type_d'exception)
{
    traitement_de_l'exception
}
finally
{
    que_tout_se_passe_bien_ou_non_on_passe_ici
}

- avec un throw

type fonction_qui_contient_le_code_à_exception() throws le_type_d'exception{
Bref on rajoute cela à la ligne de déclaration de la fonction

Que faire dans un catch(Exception e) ? Soit un System.out.println(e.toString()) soit un System.out.println(e.printStackTrace()). Je vous laisse un peu fouiller sur Internet quand même ;)

Résumé et quelques notes supplémentaires
Toutes les classes héritent de la classe de base, la classe Object (la superclasse).
Cette classe possède des méthodes qui peuvent s'avèrer très utiles (clone() et toString()).
Par convention on cache les attributs et on ne montre que les méthodes. L'ensemble des méthodes visibles constituent "l'interface".
Le polymorphisme est le fait qu'un objet d'une classe donnée peut être vu comme une instance d'une de ses classes mère.
La redéfinition est le fait de recréer une méthode déjà définie dans la classe mère.
Le mot clé this désigne la classe en cours et le mot clé super désigne la classe mère.
Le constructeur d'une classe commence par appeler les constructeurs des classes dont il hérite.
La surcharge consiste à donner le même nom à des fonctions d'une même classe mais avec des profils (type et nombre d'arguments) différents.

Voilà, j'ai pas tout expliqué et il se peut que j'ai dit quelques conneries mais vous avez là les notions importantes de la POO en Java.
Passez maintenant à l'étude d'un petit programme Java histoire de mettre en pratique ce que l'on a vu.