Bas de page
Page précédente Sommaire Page suivante
Bas de page

III) ELEMENTS DE PROGRAMMATION

4) Les objets



4-1) Définition

Un objet est une instance de classe.

class Circle()
{// le nom de mon objet commence par une majuscule
public double x, y; // Coordonnée du centre
private double r; // rayon du cercle
public Circle(double r) {
this.r = r;
}
public double area() {
return 3.14159 * r * r;
}
}
public class MonPremierProgramme() {
public static void main(String[] args) {
Circle c; // c est une référence sur un objet Circle, pas
un objet
c = new Circle(5.0); // c référence maintenant un objet alloué en
mémoire
c.x = c.y = 10;
System.out.println("Aire de c :" + c.area());
}
}


4-2) Classe de description d'une classe.

Il est possible de créer un objet classe au moins de trois façons :
_à partir d'une instance
String s = "";
Class c = s.getClass();
_à partir du nom de la classe
Class c1 = String.class;
Class c2 = int.class;
Class c3 = Class.forName("String");

Une fois un objet de classe Class obtenu, il est possible de faire de l'introspection (ou réflexion) sur les classes,
c'est à dire trouver dynamiquement les membres ou constructeurs de la classe et d'y accéder. Dans l'exemple
suivant on commence par récupérer la méthode toString() de la classe String puis on l’utilise pour afficher
le contenu de l’objet sur la sortie standard.

try
{
Class c = String.class;
Object str = c.newInstance();
java.lang.reflect.Method m [] = c.getDeclaredMethods();
Method m = c.getDeclaredMethod("toString",null);
m.invoke(str,null);
for (int i=0 ; true ; i++)
{
System.out.println(m[i].toString());
}
}
catch(IndexOutOfBoundsException i)
{
//tout s'est bien déroulé
System.exit(0);
}


Ceci pose des problèmes d'accès aux méthodes et aux constructeurs. Pour plus de détails se référer à la
documentation de java.lang.Class et paquetage java.lang.reflect.

4-3) Les Tableaux

Un tableau est considéré comme un objet. Il est manipulé via une référence. Les tableaux Java sont alloués
dynamiquement (comme malloc en C ou new en C++). Cela évite une des principales utilisation des pointeurs
en C.

Déclaration :
int [] array_of_int;
Color[][][] rgb_cube;

Création et initialisation par défaut :
array_of_int = new int[42];
rgb_cube = new Color[256][256][256];
System.out.println(array_of_int[1] == 0); // affiche 'true'
System.out.println(rgb_cube[0][0][0] == null); // affiche 'true'

Création et initialisation personnalisée :
int[] primes = {1, 2, 3, 5, 7, 7+4};
int [] primes2 = new int[] {1, 2, 3, 5, 7, 7+4};

Utilisation :
1 - Connaissance de la taille du tableau
System.out.println(array_of_int.length); // affiche '42'

2 - Vérification des bornes du tableau
arrayOfInt[0] = 1;// Affecte la valeur 1 à la première case du tableau
arrayOfInt[42] = 1; // Lève une exception ArrayIndexOutOfBoundsException

4-4) Création d'objets

Un objet est créé en instanciant une classe (Circle). Celle-ci est le modèle de création de l'objet. Dans celle-ci
sont décrits les états de l'objet (les variables d'instance) et les fonctions de manipulation de l'objet (méthodes), de
plus elle encapsule l'initialisation des objets de cette classe via les constructeurs, des variables et des méthodes
liées à la classe (static).

class Circle
{
double x, y, r;
public Circle(double x1, double y1, double r1)
{
x = x1;
y = y1;
r = r1;
}
void move(x,y)
{
this.x = x;
this.y = y;
}
}

L'opérateur new est appliqué sur un des constructeurs (« fonctions » sans type de retour et de même nom que la
classe) de la classe (new Circle(0,0,1)). Une nouvelle instance de la classe (objet) est alors allouée en
mémoire et initialisée par le constructeur.
Pour manipuler l'objet ainsi créé, une référence sur l'objet est utilisée (Circle c).

Circle c = new Circle(0,0,1);

En Java le nom de la classe est également le type d'une variable qui peut contenir la référence à un objet de cette
classe. Toute classe possède un constructeur par défaut (si aucun autre constructeur n'est défini), implicite, qui
effectue l'allocation et l'initialisation par défaut des variables d'instance de l'objet (false pour les booléens, 0
pour les types primitifs numériques, null pour les références).

4-5) Destruction d'objets

En java, la destruction des objets est prise en charge par le garbage collector (ramasse-miettes en français). Il
s'agit d'un thread basse priorité qui effectue les destructions de manière asynchrones
Le GC détruit les objets pour lesquels il n'existe plus de référence.
Les destructions sont asynchrones (le GC est géré dans une thread de basse priorité).
Aucune garantie n'est apportée quant à la destruction d'un objet.
Si l'objet possède la méthode finalize, celle-ci est appelée lorsque l'objet est détruit.

public class Circle
{
...
void finalize()
{
System.out.println("Je suis garbage collecte");
}
}
...
Circle c1;
if (condition)
{
Circle c2 = new Circle(); // c2 référence une nouvelle instance
c1 = c2;
}
// La référence c2 n'est plus valide mais il reste une référence,c1,
// sur l'instance
c1=null; // L'instance ne possède plus de référence. Elle n'est plus
// accessible. A tout moment le gc peut detruire l'objet.


4-6) Les membres

Les membres (attributs ou méthodes) sont accessibles par leur nom à l'intérieur de l'objet ou de la classe (static).
En dehors, ceux-ci sont accédés via une instance de la classe ou via la classe pour les membres statiques.

Circle c = new Circle(0,0);
c.r = 3; // On accède à l'attribut r de l'objet référencé par c
c.move(1,2); // On invoque la méthode move de l'objet référencé par c
pi = Math.PI; // On accède a l'attribut statique de la classe Math
r = Math.sqrt(2); // On invoque la méthode statique de la classe Math

L'accessibilité des membres est pondérée par des critères de visibilité (public, private, protected, ...). Dans une
classe il est possible de faire référence à l'objet courant en utilisant le mot clef this. Ceci est particulièrement utile
lorsque un paramètre ou une variable locale masque le membre. De plus, this() permet d'accéder à un
constructeur de l'objet (il doit être la première instruction).

class Point
{
int x,y;
Point()
{
this(0,0);
}
Point(int x,int y)
{
this.x=x;
this.y=y;
}
}


4-7) Les membres statiques

Les attributs membres statiques sont communs à toutes les instances de la classe. Ceux-ci sont précédés dans la
définition de classe par le mot clef static. Il n'est pas nécessaire d'instancier la classe pour accéder à un
membre statique. Les méthodes statiques ne peuvent pas accéder à this. Une constante est une variable static
final.

class Circle
{
public static int num_circle = 0;
public static final double PI = 3.14159;
public double x, y, r;
public Circle(double r)
{
this.r = r;
num_circle++;
}
public Circle bigger(Circle c)
{
if (c.r > r)
{
return c;
}
else
{
return this;
}
}
public static Circle bigger(Circle c1, Circle c2)
{
if (c1.r > c2.r)
{
return c1;
}
else
{
return c2;
}
}
}
Circle c1 = new Circle(2.0);
Circle c2 = new Circle(3.0);
System.out.println("Nombre de cercle : " + Circle.num_circle);
Circle c3 = c1.bigger(c2); // c3 = c2
Circle c4 = Circle.bigger(c1, c2); // c4 = c2;

4-8) Les méthodes

Les méthodes sont déclarées comme des fonctions en C. Comme en C et C++ le passage de paramètres se fait par
valeur. Les objets ne sont accessibles que par une référence, il y a passage de références par valeurs, donc tout ce
passe comme un passage par référence.

class Int
{
int val;
Int(int val)
{
this.val = val;
}
}
class C
{
void m(int i1, Int i2,Int i3)
{
i1++;
i2.val++;
i3=new Int(i3.val);
i3.val++;
}
void main(String []argv)
{
int a = 0;
Int b = new Int(0);
Int c = new Int(0);
m(a, b,c);
System.out.println("a="+a+",b="+b.val+",c="+c.val); // a=0, b=1, c=0
}
}


Il est toujours possible de déclarer des variables locales à la méthode. Contrairement au C une variable locale ne
peut pas être static. Pour obtenir le même effet il suffit d'utiliser une variable de classe (static). Une
variable locale peut être déclarée final ce qui veut dire qu'elle est à affectation unique (non mutable).
L'initialisation peut avoir lieu en dehors de sa déclaration (depuis le JDK 1.1). Les méthodes ont un nombre fixe
d'arguments.

4-9) La surcharge de méthode et de constructeur

Dans une classe, plusieurs méthodes ou constructeurs peuvent coexister en ayant un même nom, si elles ont des
profils différents. Le profil est constitué du nom de la méthode ou du constructeur et du type de chacun de ces
paramètres.

Ceci permet d'obtenir une forme de polymorphisme paramétrique.
Deux méthodes de même nom et de mêmes types de paramètres ne peuvent coexister dans une classe, même si le
type de retour ou les exceptions levées sont différentes.

4-10) La destruction des objets

La destruction des objets est implicite. Elle est effectuée automatiquement par le ramasse-miettes (garbage
collector). Le ramasse-miette récupère l'espace occupé par les objets sur lesquels il n'existe plus de référence.
Pour limiter la durée de vie des objets affecter null aux références dès que vous n'avez plus besoin de la
référence. Le ramasse miette est un processus léger (thread) de basse priorité qui effectue les destructions de
façon asynchrones. Aucune garantie n'est apportée quant à la destruction d'un objet.

Si l'objet possède la méthode finalize(), elle est appelée au moment de la destruction (pour fermer des
fichiers ou une connexion par exemple). Toute référence à laquelle est affectée this prend la valeur null en
dehors de finalize pour éviter tout problème.

public class Circle
{
...
void finalize()
{
System.out.println("je suis garbage collecte");
...
}
}
...
Circle c1;
if (condition)
{
Circle c2 = new Circle(); // c2 référence une nouvelle instance de la classe Circle
c1 = c2; // c1 référence le même objet que c2
}
// La référence c2 n'est plus utilisée par la suite donc elle peut être détruite à tout moment. En revenche, il reste
une référence (c1) sur l'instance
c1 = null; // L'instance ne possède plus aucune référence. Elle n'est plus accessible.
// A tout moment, le ramasse miette peut détruire l'objet


Il est possible de forcer l'appel au ramasse-miettes : System.gc().

4-11) Zone d'initialisation de classe et d'instance

On peut spécifier une zone de code statique dans une classe afin d'initialiser certaines variables de classe
statiques tel qu'on le ferait avec un constructeur pour les variables d'instances.

class C
{
static Vector v1 = new Vector();
static
{
v1.addElement(new Integer(1));
}
Vector v2 = new Vector();
static
{
v2.addElement(new Integer(1));
} // executé par chaque constructeur après super
}


4-12) L'héritage

_Une classe ne peut hériter que d'une seule classe : héritage simple (pas d’héritage multiple comme
en C++) ;
_Toutes les classes dérivent, par défaut, de la classe Object ;
_Une référence de type C peut contenir des instances de la classe C et de toute classe dérivant de C
(sous-typage) ;
_Il est possible dans un constructeur de classe d'appeler un constructeur particulier de la super classe
au moyen de super(...) (comme première instruction), sinon le constructeur par défaut est appelé ;
_Les classes final ne peuvent pas être dérivées. Une méthode déclarée final ne peut pas être
masquée par une classe fille (cela afin d'obtenir un code plus efficace car la recherche de méthode
peut être statique).

Exemple :

class Ellipse
{
public double r1, r2;
public Ellipse(double r1, double r2)
{
this.r1 = r1; this.r2 = r2;
}
public double area()
{
...
}
}
final class Circle extends Ellipse
{
public Circle(double r)
{
super(r, r);
}
public final double getRadius()
{
return r1;
}
}
Ellipse e = new Ellipse(2.0, 4.0);
Circle c = new Circle(2.0);
System.out.println("aire de e: " + e.area() + ", aire de c :" + c.area());
System.out.println((e instanceof Circle)); // false
System.out.println((e instanceof Ellipse); // true
System.out.println((c instanceof Circle)); // true
System.out.println((c instanceof Ellipse)); // true (car Circle dérive de
Ellipse)
e = c; // e reference l'instance c en
tant qu'ellipse
System.out.println((e instanceof Ellipse)); // true
System.out.println((e instanceof Circle)); // true
double r = e.getRadius(); // error: Method getRadius() not found in class Ellipse
c = e; // error: Incompatible type for =. Explicit cast needed.


4-13) La surcharge

Une classe peut redéfinir les membres de ses classes ancêtres. On appelle cela la surcharge ou la surdéfinition.
Les deux membres coexistent, une des difficultés de la programmation en Java est de savoir lequel est choisi (et
encore ceci est simple comparé à C++ ou la surchage d’opérateur, l’héritage combiné à quelques #define bien
sentis contribuent à l’illisibilité du code).
_Dans une classe l'accès aux membres hérités masqués peut se faire en utilisant le mot clef super.
_Deux variables d'instance masquées peuvent avoir des types différents.
_Le transtypage d'une référence permet d'accéder à la variable d'instance de la classe "concernée".
_Le transtypage d'une référence ne permet pas d'accéder à la méthode de la classe "concernée".
_Une variable d'instance masquée utilisée dans une classe est celle "connue" statiquement.
_Une méthode masquable (ni final, ni private) utilisée dans une classe n'est "connue" que
dynamiquement (comme les méthodes virtual de C++).
_Une méthode qui en masque une autre doit avoir une signature au moins aussi précise que celle
qu'elle masque à savoir : même profil sinon surcharge, même type de retour, même « staticité »,
levée d'un sous-type des exceptions levées par celle qu'elle masque.

Exemple :

class A
{
int x;
}
class B extends A
{
Object x;
void m()
{
}
Object m1()
{
return new Object();
}
void m2()
{
}
protected void m3()
{
}
void m4() throws Exception
{
}
}
class C extends B
{
int x;
void m() {}
int [] m1()
{
return new int[1];
} // Erreur car type de retour différents
void m2() throws IOException
{} // Erreur car m2 ne lève pas d'exception
void m3()
{}// Erreur car visibilité plus restreinte que protected
protected void m4() throws IOException {} // Ok car IOException
sous-type de Exception
void main(String [] argv)
{
x; // attribut x de la classe C
this.x; // idem
super.x; // attribut x de la classe B
super.super.x; // erreur
((B)this).x; // attribut x de la classe B
((A)this).x; // attribut x de la classe A
m(); // Référence la méthode m de la classe de
//this qui ne sera connu que dynamiquement
super.m() // Référence la methode m de la classe B
super.super.m(); // erreur
((B)this).m(); // Référence la méthode m de la classe de
//this (et non de la classe B)
}
}


4-14) L'appel de méthodes

La recherche de méthode se décompose en trois phases :
1. À la compilation, le type déclaré A de la référence sur laquelle est appelée la méthode est déterminé.
2. À la compilation, dans la classe A la méthode accessible dont le profil (type des paramètres) est le plus précis
compatible avec les arguments est déterminée (ambiguïté possible).
3. Dynamiquement, la méthode déterminée statiquement est recherchée en fonction de la classe réelle de l'objet
référencé.

public class A
{
void m(A a)
{
System.out.println("A");
}
}
public class B extends A
{
void m(B b)
{
System.out.println("B");
}
public static void main(String args[])
{
B b1 = new B();
A b2 = new B();
b1.m(b2);
}
}


Il est déterminé statiquement (en fonction des types déclarés) que la méthode à appeler est de type void m(A).
Dynamiquement, la seule méthode qui ait ce type est celle de la classe A. La méthode m n'est pas masquée mais
surchargée dans B.

public class C extends B
{
void m(A a)
{
System.out.println("C");
}
public static void main(String args[])
{
B c1 = new C();
A c2 = new C();
c1.m(c2);
}
}


Il est déterminé statiquement (en fonction des types déclarés) que la méthode à appeler est de type void m(A).
Dynamiquement, dans C la méthode m masque celle de A.

4-15) La sérialisation

Le langage Java propose un mécanisme de sérialisation permettant d'enregistrer ou de récupérer des objets dans
un flux. Cette caractéristique est très intéressante en particulier pour les applications nécessitant une certaine
persistance des objets ou pour envoyer des objets sur le réseau.
La sérialisation d'un objet est effectuée lors de l'appel de la méthode writeObject() sur un objet
implémentant l'interface ObjectOutput (par exemple un objet de la classe ObjectOutputStream). La
désérialisation d'un objet est effectuée lors de l'appel de la méthode readObject() sur un objet implémentant
l'interface ObjectInput (par exemple un objet de la classe ObjectInputStream).

Lorsqu'un objet est sauvé, tous les objet qui peuvent être atteins depuis cet objet sont également sauvés. En
particulier si l'on sauve le premier élément d'une liste tous les éléments sont sauvés. Tout objet n'est sauvé qu'une
fois grâce à un mécanisme de cache. Il est possible de sauver une liste circulaire. Pour qu'un objet puisse être
sauvé ou récupéré sans lever une exception NotSerializableException il doit implémenter l'interface
Serializable ou l'interface Externalizable. L'interface Serializable ne contient pas de méthode.

Tout objet implémentant l'interface Serializable peut être enregistré ou récupéré même si une version
différente de sa classe (mais compatible) est présente. Le comportement par défaut est de sauvegarder dans le
flux tous les champs qui ne sont pas static, ni transient. Des informations sur la classe (nom, version), le
type et le nom des champs sont également sauvegarder afin de permettre la récupérer de l'objet.
Si l'objet implémente les méthodes :
_private void writeObject(ObjectInputStream s) throws IOException
_private void readObject(ObjectOutputStream s) throws IOException

celles-ci sont appelées pour sauvegarder ou lire les champs propres de la classe (pas ceux de la classe héritée)
plutôt que les méthodes par défaut. Ceci peut servir pour sauvegarder des informations complémentaires ou
sauvegarder des champs non sérialisables.

Les méthodes suivantes sont équivalentes à la sauvegarde par défaut :

private void writeObject(ObjectInputStream s)
throws IOException
{
s.defaultWriteObject();
}
private void readObject(ObjectOutputStream s)
throws IOException
{
s.defaultReadObject();
}


L'interface Externalizable laisse toute liberté (et problèmes) à l'utilisateur. Pour sauver un tel objet les
méthodes :
_ public void writeExternal(ObjectOutput s) thows IOException
_ public void readExternal(ObjectInput s) thows IOException
sont utilisées. Toutes les sauvegardes et lecture, en particulier celles des super-classes, sont laissées à la charge
de l'utilisateur. De plus toute sous-classe peut masquer ces méthodes ce qui peut poser des problèmes de sécurité.
Une certain nombre de classes ne sont pas sérialisations :
_ Thread ;
_ InputStream et OutputStream ;
_ Peer ;
_ JDBC.

4-15-1) La sauvegarde des objets

Chaque fois qu'un objet est sauvé dans un flux, un objet handle, unique pour ce flux, est également sauvé. Ce
handle est attaché à l'objet dans une table de hashage. Chaque fois que l'on demande de sauver à nouveau l'objet,
seul le handle est sauvé dans le flux. Ceci permet de casser les circularités.

Les objets de la classe Class sont sauvegardés et restaurés de façon particulière. L'objet Class n'est pas sauvé
ou récupéré directement dans le flux. Un objet de la classe OjectStreamClass qui lui correspond prend sa
place. Cette classe contient les méthodes suivante :

_public static ObjectStreamClass lookup(Class cl) retourne l'objet
ObjectStreamClass qui correspond à l'objet Class passé en paramètre si la classe
implémente java.io.Serializable ou java.io.Externalizable.
_public String getName() retourne le nom de la classe correspondante.
_public long getSerialVersionUID() retourne le numéro de version de l'objet classe.
Deux classes sont compatibles si elles ont même numéro de version.
_public Class forClass() retourne l'objet Class qui correspond à l'objet courant.
_public String toString() affiche une description de l'objet.
Lors de l'écriture dans le flux d'un tel objet sont écrits dans le flux :
_son nom ;
_le serialVersionUID ;
_le nombre de champs sauvegardables ;
_la liste des noms, types et modifieur des champs ;
_l'objet ObjectStreamClass de la super-classe.

Il est possible d'ajouter des informations dans le flux. Par exemple une URL où récupérer le .class correspondant,
comme dans le cas des RMI, ou bien le .class lui-même. Pour cela il faut masquer dans le classe
ObjectOutputStream la méthode :
protected void annotateClass(Class cl) throws IOException
Les objets de la classe String sont sauvés sont la forme UTF-8.
Les tableaux sont sauvés sous la forme : ObjectStreamClass correspondant, suivi de la taille du tableau et
des éléments.

Pour les autres objets le mécanisme de sauvegarde est le suivant si l'objet est sérialisable :
_sauvegarde de l'objet ObjectStreamClass de la classe la plus précise de l'objet implémentant
Serializable ;
_Pour cette classe et ses super-classes les champs sont sauvés dans un ordre particulier par la
méthode defaultWriteObject ou par la méthode writeObject si elle a été définie (la
méthode readObject devra les lire dans le même ordre). Si les champs sont d'un type primitif la
méthode write* correspondante est appelée.

Si la classe implémente Externalizable, seule la méthode writeExternal est appelée. Dans chacun de
ces cas si l'objet n'est pas sauvegardable (n'implémente pas Serializable, ni Externalizable) une
exception NotSerializableException est retournée.

4-15-2) La sécurité

La sécurité au niveau des classes chargées est la même que celle qui existe par défaut. En effet, le code des
classes n'est pas par défaut stocké dans le flux mais récupéré par un mécanisme externe que peut contrôler
l'utilisateur.
En revanche, il faut bien être conscient que tous les champs sont a priori sauvés dans le flux, en particulier les
champs private. Par défaut, une classe ne peut pas être sauvée, il faut qu'elle implémente une des interfaces
Serializable ou Externalizable, les objets de classes vraiment sensibles ne peuvent donc pas être
sauvés (sauf demande explicite du programmeur). Si le programmeur veut quand même sauver un tel objet il doit
marquer transient les champs qu'il ne vaut pas voir apparaître pas dans le flux.
Haut de page
Page précédente Sommaire Page suivante
Haut de page
Contactez-nous
Conditions d'utilisation
Qui sommes nous?
© 2001 IsepFAQtory Tous droits réservés