Dans cette section, nous examinons la dialectique question de la classe et de l’objet.
Sans même construire des objets nous mêmes, nous en utilisons forcément, car l’environnement d’exécution de Java est principalement en style objet. Autrement dit on sait déjà que les objets ont des méthodes.
out.println()
.
Les objets possèdent aussi des champs également appelés variables
d’instance, auxquels on accède par la notation en « . » comme
pour les variables de classes.
Par exemple, si t est un tableau t.length
est la
taille du tableau.
La bibliothèque de Java emploie énormément les objets. Par exemple, le point est un objet de la classe Point du package java.awt. On crée un nouveau point par un appel de constructeur dont la syntaxe est :
On peut aussi créer un point en donnant explicitement ses coordonnées (entières) au constructeur :
On dit que le constructeur de la classe Point est surchargé (overloaded), c’est-à-dire qu’il y a en fait deux constructeurs qui se distinguent par le type de leurs arguments. Les méthodes, statiques ou non, peuvent également être surchargées.
On peut ensuite accéder aux champs d’un point à l’aide de la notation usuelle. Les points ont deux champs x et y qui sont leurs coordonnées horizontales et verticales :
Les points possèdent aussi des méthodes, par exemple la méthode
distance,
qui calcule la distance euclidienne entre deux points, renvoyée comme
un flottant double précision double
.
Un moyen assez compliqué d’afficher une approximation de √2
est donc
Les objets sont reliés aux classes de deux façons :
Point
. Dans certaines
conditions (voir les sections B.2.3
et 3.1.2),
la classe-type peut changer au cours de la
vie l’objet.
Le plus souvent, cela signifie qu’un objet dont la classe reste
immuable, peut, dans certaines conditions, être rangé dans une
variable dont le type est une autre classe.
Un premier exemple (assez extrême) de cette distinction est donné par
null
. La valeur null
n’a ni classe d’origine (elle n’est
pas créé par
new
), ni champs, ni méthodes, ni
rien, mais alors rien du tout. En revanche,
null
appartient à toutes les classes-types.
Notre idée est de montrer comment créer des objets très similaires aux points de la section précédente. On crée très facilement une classe des paires (d’entiers) de la façon suivante :
Nous avons partout explicité les accès aux variables d’instance
et par this.x
et this.y
.
Nous nous sommes aussi laissés aller à employer la notation
this(0,0)
dans le constructeur sans arguments, cela permet
de partager le code entre constructeurs.
On remarque que les champs x et y ne sont pas introduits par le mot-clé static. Chaque objet possède ses propres champs, le programme
affiche « 0, 3
», ce qui est somme toute peu surprenant. De
même, la méthode distance est propre à chaque objet, ce qui est
logique. En effet, si p
est une autre paire, les distances
p1.distance(p)
et p2.distance(p)
n’ont pas de raison
particulières d’être égales.
Rien n’empêche de mettre des membres static
dans une classe à créer des objets.
Le concepteur de la classe Pair
peut par exemple se dire qu’il
est bien dommage de devoir créer deux objets si on veut simplement
calculer une distance. Il pourrait donc inclure la méthode statique
suivante dans sa classe Pair
.
Il serait alors logique d’écrire la méthode distance dynamique plutôt ainsi :
Où bien sûr, distance (this.x,
… se comprend
comme Pair.distance (this.x,
…
L’héritage dans toute sa puissance
sera abordé au cours suivant.
Mais nous pratiquons déjà l’héritage sans le savoir.
En effet,
les objets des classes que nous écrivons ne démarrent pas tout
nus dans la vie.
Toute classe hérite implicitement (pas besoin de extends,
voir la section B.1.5)
de la classe Object.
Les objets de la classe Object (et donc tous les objets)
possèdent quelques méthodes,
dont la fameuse méthode toString.
Par conséquent, le code suivant est accepté par le compilateur,
et s’exécute sans erreur.
Tout se passe exactement comme si nous avions écrit une méthode toString
,
alors que cette méthode est en fait héritée.
Ce que renvoie la méthode toString
des Object
n’est pas bien beau.
Pair@10b62c9
On reconnaît le nom de la classe Pair
suivi d’un nombre en hexadécimal
qui est plus ou moins l’adresse dans la mémoire de l’objet dont on a
appelé la méthode toString
.
Mais nous pouvons redéfinir (override) la méthode
toString
dans la classe Pair
.
Et l’affichage de p.toString()
produit cette fois ci le résultat bien
plus satisfaisant (1, 0)
.
Même si c’est un peu bizarre, il n’est au fond pas très surprenant que
lorsque nous appelons la méthode toString
de l’intérieur
de la classe Pair
comme nous le faisons ci-dessus, ce soit la
nouvelle méthode toString
qui est appelée.
Mais écrivons maintenant plus directement :
Et nous obtenons une fois encore l’affichage (1, 0)
.
Or, nous aurons beau parcourir la liste des neuf définitions de méthodes
print
surchargées de la classe PrintStream
,
de print(boolean b)
à print(Object obj)
, nous n’avons bien évidemment
aucune chance d’y trouver une méthode print(Pair p)
.
Mais un objet de la classe Pair
peut aussi être vu comme un objet
de la classe Object
. C’est le sous-typage :
le type des objets Pair
est un sous-type du type des objets Object
(penser sous-ensemble : Pair
⊂ Object
).
En Java, l’héritage entraîne le sous-typage,
on dit alors plus synthétiquement que Pair
est une
sous-classe de Object
.
On note que le compilateur procède d’abord à un sous-typage
(il se dit qu’un objet Pair
est un objet Object
), pour
pouvoir résoudre la surcharge (il sélectionne la bonne méthode print
parmi les neuf possibles).
La conversion de type vers un sur-type est assez discrète, mais on
peut l’expliciter.
C’est donc finalement la méthode print(Object obj)
qui est
appelée. Mais à l’intérieur de cette méthode, on ne sait pas que
obj est en fait un objet Pair
. En simplifiant un peu, le
code de cette méthode est équivalent à ceci
C’est-à-dire que, au cas de null
près, la méthode
print(Object obj)
appelle print(String s)
avec
comme argument obj.toString()
. Et là, il y a une petite surprise :
c’est la méthode toString()
de la classe d’origine (la « vraie »
classe de obj
) qui est appelée, et non pas la méthode
toString()
des Object
. C’est ce que l’on appelle
parfois la liaison tardive.