On peut considérer un objet comme un ensemble d'informations liées les unes
aux autres, associé à des mécanismes de manipulation des informations en
question. Un exemple simple est fourni par les objets String
de Java. Dans
ce cas, on a :
En Java, les méthodes d'un objet (appelées méthodes d'instance) réalisent les manipulations des informations contenues dans l'objet.
Pour définir informatiquement un objet, il faut décrire l'ensemble
d'informations qui le constitue et les mécanismes de manipulation de ces
informations. Cette description est fournie par une Classe en Java. Par
exemple, il existe une classe String
qui décrit les objets String
. La
classe est en fait le type de l'objet et on dit que l'objet est une
instance de la classe, simplement pour insister sur le fait que son contenu
et son comportement sont décrits par la classe (qui joue donc le rôle de
patron/modèle pour les objets).
Sauf pour le type String
, la création d'un objet se fait toujours avec
l'instruction new
, en utilisant la forme générale
new {nom de la classe de l'objet}({paramètres})
dans laquelle
paramètres
désigne d'éventuels paramètres de création de cet objet. Par exemple :
Scanner scan = new Scanner(System.in);
s'interprète comme la création d'un objet de type Scanner
paramétré pour
lire le clavier (ce qui est représenté par le paramètre System.in
).
Le résultat de la création d'un objet est un pointeur (une référence dans le
vocabulaire Java) vers l'objet créé sur le tas. Dans l'exemple au dessus, on
place ce pointeur dans une variable scan
de type Scanner
(elle aussi).
Dans le cas spécifique des String
, on peut utiliser des valeurs littérales
en écrivant par exemple
String s = "Toto";
mais rien n'empêche d'utiliser new
comme dans la version suivante de
l'exemple
String s = new String("Toto");
Attention, les résultats sont subtilement différents…
Pour manipuler les informations contenues dans un objet, on utilise des
méthodes d'instance avec une syntaxe générale de la forme
{référence vers un objet}.{méthode}({paramètres})
comme dans l'exemple
suivant :
public class DemoString {
public static void main(String[] args) {
String s = new String("Bonjour");
System.out.println(s.length());
System.out.println(s.charAt(0));
System.out.println(s.toUpperCase());
System.out.println("Toto".substring(2));
}
}
qui affiche
7 B BONJOUR to
Un principe fondamental de la programmation orientée objet est celui de l'identité : chaque objet est unique, même si les informations qu'il représente sont identiques à celle d'un autre objet. C'est ce principe qui explique que le programme suivant
public class Identity {
public static void main(String[] args) {
String s = new String("Bonjour");
String t = new String("Bonjour");
System.out.println(s == t);
}
}
affiche
false
En effet, bien que s
et t
désignent des objets représentant tous les deux
le texte Bonjour, ces deux objets sont distincts, ce qui se manifeste par
des références (pointeurs) différentes. La comparaison des variables s
et t
s'effectuant sur le contenu de ces variables, elle renvoie la valeur de vérité
faux puisque ces références sont différentes.
En Java, le principe d'identité s'applique aussi aux tableaux bien que ces derniers ne soient pas vraiment des objets. Ceci explique pourquoi le programme suivant
public class ArrayIdentity {
public static void main(String[] args) {
int[] x = { 1, 2 };
int[] y = { 1, 2 };
System.out.println(x == y);
}
}
affiche
false
Le principe d'identité est parfois gênant en pratique. Pour contourner cette
limitation, les objets possèdent une méthode equals
. Dans la plupart des
cas, la méthode equals
a la même sens que la comparaison obtenue par
==
. Cependant, pour certains objets, la méthode correspond bien
à une comparaison des informations associées aux objets. C'est le cas pour les
String
, par exemple. Le programme suivant
public class StringEquals {
public static void main(String[] args) {
String s = new String("Bonjour");
String t = new String("Bonjour");
System.out.println(s == t);
System.out.println(s.equals(t));
}
}
affiche
false true
En effet, la première comparaison s'effectue sur les objets en tant
qu'entités. En application du principe d'identité, on obtient donc false
. Au
contraire, la deuxième comparaison utilise la méthode equals
qui réalise
ainsi une comparaison des informations, soit ici du texte représenté par
chaque chaîne, ce qui conduit naturellement au résultat true
.
Pour les tableaux, on dispose d'une méthode particulière dans la classe
Arrays
, comme le montre l'exemple suivant :
import java.util.Arrays;
public class ArrayEquals {
public static void main(String[] args) {
int[] x = { 1, 2 };
int[] y = { 1, 2 };
System.out.println(x == y);
System.out.println(Arrays.equals(x,y));
}
}
qui affiche
false true
pour les mêmes raisons que dans l'exemple des String
.
equals
dépend du type objet et ne
correspond pas nécessaire à la comparaison du contenu.
Pour faciliter l'interaction avec l'utilisateur, et même la programmation, il
est pratique de pouvoir afficher les informations contenues dans (représentées
par) un objet. Or, Java ne sait afficher que les chaînes de caractères
(String
) et les types fondamentaux (int
, double
, etc.). Plus
précisément, tout objet possède une méthode toString()
qui produit une
chaîne de caractères censée représenter les informations associées à
l'objet. Quand on tente d'afficher un objet (dans un System.out.println
, par
exemple), Java utilise automatiquement cette méthode. Malheureusement, le
résultat n'est pas très utile en général, comme le montre l'exemple
suivant. Le programme proposé crée un objet Random
(qui fabrique des nombres
aléatoires) et tente de l'afficher :
import java.util.Random;
public class RandomPrint {
public static void main(String[] args) {
Random rng = new Random();
System.out.println(rng);
}
}
On obtient :
java.util.Random@57543bc5
Comme pour les tableaux, le résultat n'est pas très utile. Il correspond au
type de l'objet (avec son package ici) suivi d'une valeur entière
correspondant au hash de l'objet écrit en hexadécimal (en général, ce hash
est l'adresse mémoire de l'objet). Notons que tout se passe comme si on avait
écrit System.out.println(rng.toString())
et donc que le problème vient de la
méthode toString()
dont l'intérêt pratique est en général assez limité.
Pour beaucoup d'objets cependant, la méthode toString()
donne quelque chose
de relativement utile. Le programme
import java.util.Calendar;
import java.util.Date;
public class CalendarDemo {
public static void main(String[] args) {
Calendar maintenant = Calendar.getInstance();
System.out.println(maintenant);
Date today = maintenant.getTime();
System.out.println(today);
}
}
produit l'affichage complexe suivant :
java.util.GregorianCalendar[time=1351505071671,areFieldsSet=true,areAllFieldsSet=true,lenient=true,zone=sun.util.calendar.ZoneInfo[id="Europe/Paris",offset=3600000,dstSavings=3600000,useDaylight=true,transitions=184,lastRule=java.util.SimpleTimeZone[id=Europe/Paris,offset=3600000,dstSavings=3600000,useDaylight=true,startYear=0,startMode=2,startMonth=2,startDay=-1,startDayOfWeek=1,startTime=3600000,startTimeMode=2,endMode=2,endMonth=9,endDay=-1,endDayOfWeek=1,endTime=3600000,endTimeMode=2]],firstDayOfWeek=2,minimalDaysInFirstWeek=4,ERA=1,YEAR=2012,MONTH=9,WEEKOFYEAR=44,WEEKOFMONTH=5,DAYOFMONTH=29,DAYOFYEAR=303,DAYOFWEEK=2,DAYOFWEEKINMONTH=5,AMPM=0,HOUR=11,HOUROFDAY=11,MINUTE=4,SECOND=31,MILLISECOND=671,ZONEOFFSET=3600000,DSTOFFSET=0] Mon Oct 29 11:04:31 CET 2012
L'objet Date
est donc affiché en utilisant les conventions américaines
(deuxième objet). Le premier affichage décrit en détail l'objet Calendar
. Il
s'agit ici du calendrier grégorien (standard en occident), ainsi que diverses
informations contenues dans l'objet, comme la date courante, la zone
temporelle, etc.
La classe d'un objet joue le rôle de patron pour cet objet. Elle peut aussi
proposer des méthodes de classe, ainsi que des constantes de
classe. Contrairement aux méthodes d'instance, une méthode de classe n'est
pas appelée à partir d'un objet, mais à partir d'une classe, sous la forme
générale {Classe}.{méthode}({paramètres})
. Par exemple, la classe Math
fournit de nombreuses méthodes mathématiques de la forme Math.abs(-4)
qui
calcule la valeur absolue de son paramètre ou Math.sqrt(5)
qui calcule la
racine carrée de son paramètre.
En pratique, les méthodes de classe servent surtout à créer des objets dans
des conditions spéciales, sans utiliser la technique new {nom de la classe de l'objet}({paramètres})
.
Par exemple, la classe String
possède un ensemble de méthodes valueOf
pour
créer des chaînes de caractères à partir de valeurs de types fondamentaux. Par
exemple String.valueOf(5)
produit la chaîne de caractères ="5"=. Le
programme au dessus contient un exemple de même nature
(Calendar.getInstance()
), de même que l'exemple complet ci-dessous
(BigInteger.valueOf(k)
).
Les constantes de classe sont utilisée pour des objets particuliers. Par
exemple System.out
est un objet (de type PrintStream
) qui permet de faire
des affichages. De même BigInteger.ONE
est l'entier long 1 (cf en
dessous). Ce sont des constantes, car une affectation de la forme
BigInteger.ONE=...
est interdite.
Comme les objets sont toujours manipulés par référence, on peut être confronté aux mêmes phénomènes d'alias que pour les tableaux. Voici un exemple révélateur :
public class StringBuilderAlias {
public static void main(String[] args) {
StringBuilder s = new StringBuilder();
StringBuilder t = s; // attention, c'est le même objet
System.out.println(t==s); // comme le confirme ceci
System.out.println(s); // chaîne vide
s.append("abc");
System.out.println(s); // affiche abc
System.out.println(t); // et donc on obtient le même affichage qu'au dessus
}
}
Ce programme affiche :
true
abc abc
En effet les variables s
et t
désignent le même objet (c'est pourquoi
s==t
vaut true
). De ce fait, toute modification de cet objet
peut se « voir » en utilisant n'importe laquelle des variables. Le résultat
est surprenant car on modifie l'objet avec la variable s
et on voit le
résultat avec une autre variable t
.
Ce phénomène ne peut apparaître que si l'objet est modifiable. Or, il existe de nombreux objets immuables, c'est-à-dire dont la valeur est fixée définitivement au moment de la création. L'exemple suivant montre la différence avec le cas modifiable :
public class StringAlias {
public static void main(String[] args) {
String s = new String();
String t = s; // attention, c'est le même objet
System.out.println(t==s); // comme le confirme ceci
System.out.println(s); // chaîne vide
s = s + "abc";
System.out.println(s); // affiche abc
System.out.println(t); // affiche toujours une chaîne vide
System.out.println(t==s); // car les objets sont différents !
}
}
Ce programme affiche :
true
abc
false
Il n'existe en effet aucune méthode de modification d'un objet de type
String
. Toutes les opérations qui semblent modifier l'objet fabriquent en
fait une nouvelle chaîne, un nouvel objet String
. Ici, la variable s
contient une nouvelle chaîne "abc"
et la chaîne vide de départ n'a pas été
modifiée, ce qui explique l'affichage obtenu.
String
représente une chaîne de caractères ;String
ne sont pas modifiables après leur création ;toString
toString()
dans ce cas ;equals
equals
compare le contenu des chaînes.
La classe String
propose divers constructeurs (cf sa documentation). On
utilisera en particulier les constructeurs suivants :
String()
: création d'une chaîne vide (longueur 0) ;String(s)
: création d'une chaîne copie de la chaîne paramètre (ce sont
deux objets indépendants qui représentent le même texte) ;String(sb)
: création d'une chaîne de caractères à partir d'un
StringBuilder
(cf ci-dessous).
La classe String
propose de très nombreuses méthodes dont les suivantes sont
très utilisées :
charAt(index)
: renvoie le caractère (char
) de position index
(numérotation à partir de 0) ;concat(str)
: renvoie une nouvelle chaîne de caractères constituée de la
mise bout à bout de la chaîne appelante et de la chaîne str
;indexOf(ch)
: renvoie la position de la première apparition du caractère
ch
dans la chaîne (ou -1 si le caractère n'apparaît pas dans la
chaîne). Il existe de nombreuses variantes (recherche depuis la fin,
recherche d'une chaîne entière, etc.) ;length()
: donne la longueur de la chaîne (nombre de caractères) ;substring(beginIndex,endIndex)
: renvoie une nouvelle chaîne constituée
des caractères de numéros beginIndex
à endIndex-1
;String.valueOf(x)
: méthode de classe qui fabrique un objet String
correspondant à la représentation en chaîne de caractère de x
.StringBuilder
représente une chaîne de
caractères modifiable ;StringBuilder
sont modifiables, c'est leur
principal intérêt par rapport aux objets String
;toString
String
représentant la même chaîne que
l'objet appelant ;equals
equals
compare les références (cf la discussion sur
l'identité des objets et sur equals
).
La classe StringBuilder
propose quatre constructeurs (cf sa
documentation) dont les trois suivants sont les plus utiles :
StringBuilder()
: création d'un StringBuilder
représentant la chaîne
vide et de capacité 16 (cf ci-dessous) ;StringBuilder(capacité)
: création d'un StringBuilder
représentant la chaîne
vide et de capacité capacité
(cf ci-dessous) ;StringBuilder(s)
: création d'un StringBuilder
représentant la chaîne
de caractères donnée par l'objet s
(de type String
).
Comme un StringBuilder
est modifiable, la chaîne qu'il représente peut
changer de longueur. Pour rendre les changements plus efficaces, chaque
StringBuilder
contient de la place pour un certain nombre de caractères,
c'est la capacité de l'objet. Si on augmente la taille du StringBuilder
au delà de cette capacité, celle-ci est mise à jour automatiquement, mais il
est préférable de lui donner une valeur adaptée dès la création de l'objet.
La classe StringBuilder
propose de très nombreuses méthodes dont les
suivantes sont très utilisées :
charAt(index)
: renvoie le caractère (char
) de position index
(numérotation à partir de 0) ;append(x)
: modifie l'objet appelant en lui ajoutant (à la fin) la
représentation en chaîne de caractère de x
;insert(offset, x)
: modifie l'objet appelant en insérant à la position
offset
la représentation en chaîne de caractère de x
(la fin de la
chaîne est décalée) ;length()
: donne la longueur de la chaîne (nombre de caractères) ;setCharAt(index,ch)
: remplace le caractère de position index
par ch
(en modifiant l'objet).
Le programme suivant contient une démonstration des fonctions des StringBuilder
.
public class DemoStringBuilder {
public static void main(String[] args) {
StringBuilder s = new StringBuilder();
System.out.println(s.length());
StringBuilder t = s; // attention, même objet
t.append("bla");
System.out.println(s); // même objet !
System.out.println(t.charAt(1));
StringBuilder u = new StringBuilder("bla"); // objet différent
System.out.println(s == t); // même objet
System.out.println(t == u); // objets différents
System.out.println(t.equals(u)); // comparaison des identités
String v = u.toString();
// démonstration de l'indépendance des représentations
u.append(" et toto");
System.out.println(u);
System.out.println(v);
v = v + "bli";
System.out.println(v);
System.out.println(u); // dans l'autre sens
u.setCharAt(3,'-');
System.out.println(u);
u.insert(7,"raoul et ");
System.out.println(u);
}
}
Il affiche :
0 bla l true false false bla et toto bla blabli bla et toto bla-et toto bla-et raoul et toto
BigInteger
représente un entier naturel sans
limite de nombre de chiffres ;BigInteger
ne sont pas modifiables après leur création ;toString
equals
equals
compare les valeurs numériques des objets
concernés ;import
import java.math.BigInteger
pour
utiliser ces objets
La classe BigInteger
propose des constructeurs d'utilisation assez
technique (cf sa documentation). On leur préfère donc en général la méthode
de classe valueOf
(cf ci-dessous). On pourra aussi utiliser les
constructeurs suivants :
BigInteger(s)
: création d'un BigInteger
représentant l'entier donné
par la chaîne de caractères s
, en base 10 ;BigInteger(s, base)
: création d'un BigInteger
représentant l'entier donné
par la chaîne de caractères s
, en base base
.
La classe BigInteger
propose de très nombreuses méthodes
dont les suivantes sont très utilisées :
opération(val)
: il s'agit d'un ensemble de méthodes de calcul. Chaque
méthode renvoie le résultat d'un calcul impliquant l'objet appelant et
l'objet val
. Les opérations sont :
add(val)
: additiondivide(val)
: division (euclidienne, c'est-à-dire le quotient)multiply(val)
: multiplicationremainder(val)
: reste de la division euclidiennesubtract(val)
: soustractiondoubleValue()
: renvoie une version approchée de l'entier sous forme d'un
réel ;longValue()
: renvoie une version approchée de l'entier sous forme d'un
réel (cette approximation peut donner quelque chose de complètement faux,
attention) ;BigInteger.valueOf(val)
: méthode de classe qui fabrique un objet BigInteger
correspondant à l'entier val
.
La classe BigInteger
propose aussi trois constantes dont les noms sont
explicites : ZERO
, ONE
et TEN
.
Le programme suivant calcule la factorielle d'un entier saisie par
l'utilisateur en s'appuyant sur les objets de type BigInteger
.
import java.util.Scanner;
import java.math.BigInteger;
public class BigFact {
public static void main(String[] args) {
Scanner scan = new Scanner(System.in);
System.out.print("N = ");
int n = scan.nextInt();
BigInteger factN = BigInteger.ONE;
for(int k = 2; k <= n ; k++) {
BigInteger bK = BigInteger.valueOf(k);
factN = factN.multiply(bK);
}
System.out.println(n + "! = " + factN);
}
}
On obtient par exemple :
N = 57 57! = 40526919504877216755680601905432322134980384796226602145184481280000000000000