Les données informatiques sont construites à partir des types « primitifs » :
int
, long
float
, double
char
boolean
Les chaînes de caractères de type String
ne sont pas un type primitif. On en reparlera quand on aura mieux cerné le concept d’objet.
Dans certains cas, il est plus lisible de regrouper des types primitifs au sein d’un seul type appelé « enregistrement » :
Un point est l’aggrégation de deux flottants
class Point {
double x;
double y;
}
Un cercle est représenté par trois flottants (les coordonnées du centre et le rayon) :
class Cercle {
double cx;
double cy;
double rayon;
}
voire par un point et un flottant :
class Cercle {
Point centre;
double rayon;
}
Les enregistrements sont souvent manipulés par des références : une variable de type Point
ne contient pas les deux double
mais seulement un pointeur sur l’emplacement où ils se trouvent en mémoire.
En conséquence :
Dupliquer le contenu de la variable ne duplique pas les données pointées
Point p = q;
q.x = 10;
Une fois ce code exécuté, p.x vaut aussi 10
Écraser le contenu d’une variable n’efface pas la zone correspondante en mémoire
Point p = creerPoint(0,0);
p = creerPoint(13,14);
Le premier Point
est créé inutilement
La référence peut être null
pour indiquer qu’elle ne contient pas de donnée. On ne peut dans ce cas pas affecter ou consulter la valeur des champs.
En cas de création d’un tableau de
Point
, toutes les cases sont initialisées avecnull
Certaines information que l’on souhaite stocker dans un ordinateur n’ont pas une taille préféfinie lors de l’écriture du programme. Ça n’est que lorsque l’on exécute le programme que l’utilisateur pour fournir cette information : une carte, une liste d’élève, les données du jeu minecraft.
Pour représenter ces données, on a deux possibilités :
Utiliser des tableaux, aggrégation d’un nombre fixe mais dynamique de données de même type.
Fixe mais dynamique veut dire « inconnu lors de l’écriture du programme (dynamique) mais fixé à la création de la structure de donnée »
int[]
, Point[]
Utiliser des structures de donnée récursives
Des enregistrements qui peuvent contenir comme champ une référence veut une enregistrement de même type
class ListeChaineeDePoints {
Point valeur;
ListeChaineeDePoints valeurSuivantes;
}
voire
class ListeDoublementChaineeDePoints {
Point valeur;
ListeDoublementChaineeDePoints suivant;
ListeDoublementChaineeDePoints precedent;
}
Cette notion utilise pleinement la notion de référence et la référence null
: sans elle pour indiquer la fin de la liste, cela ne marche pas.
Notez que la plupart des structures de données complexes utilisent l’une et/ou l’autre mécanisme.
La programmation objet pousse plus loin le concept d’aggrégation de données au sein d’une entité logique.
Comme déclarer une variable de type Point
ne fait qu’allouer de la place pour une référence vers les données d’un point, il faut un mécanisme pour allouer de la place pour ces données.
new
void
)c
, on utilise this.c
Les constructeurs les plus simples :
utilisent les paramètres de la fonction pour remplir les champs :
class Point {
float x;
float y;
Point(float x, float y) {
this.x = x;
this.y = y;
}
}
Mettent des valeurs fixes dans les champs :
class Chrono {
long time;
Chrono() {
this.time = System.nanoTime();
}
}
combinent les deux
class Alarm {
long time;
long duration;
Alarm(long duration) {
this.time = System.nanoTime();
this.duration = duration;
}
}
On appelle un constructeur avec new
suivi du nom de la classe puis des paramètres du constructeur :
new Point(1,2);
new Chrono();
new Alarm(1000000);
Appeler new
alloue en mémoire de la place pour les données de l’objet puis appelle le constructeur pour les remplir :
this
représente cet emplacement mémoire
Bien évidemment, pour utiliser cet objet, il faut soit stocker sa référence dans une variable :
Chrono chrono = new Chrono();
soit passer sa référence à une fonction (pas d’exemple pour le moment).
Considérons la classe Point
ci-dessous :
class Point {
float x;
float y;
Point(float x, float y) {
this.x = x;
this.y = y;
}
}
Une classe est un type de donnée (comme int
ou float[]
) : la classe Point
des objets de type Point
Point p = new Point(10,12);
p
contient une référence vers une instance de la classe Point
; en Java, on ne manipule jamais les objets directementEn Java, les fonctions doivent se trouver au sein d’une classe.
On peut très bien créer une classe sans champs juste pour cela et c’est une bonne pratique
Elles sont déclarées comme en Javascool mais précédées par le mot-clef static
class Prog {
static double aireCercle(double rayon) {
return Math.PI*rayon*rayon;
}
static void afficheAireCercle(double rayon) {
System.out.println(aireCercle(rayon));
}
}
La méthode principale a une syntaxe spéciale : elle prend en paramètre un tableau de String
et il faut ajouter public
devant static
(un peu pour indiquer que le lanceur de programme a le droit d’y accéder)
class Prog {
public static void main(String[] args) {
double ratyon = 20;
afficheAireCercle(rayon);
}
static double aireCercle(double rayon) {
return Math.PI*rayon*rayon;
}
static void afficheAireCercle(double rayon) {
System.out.println(aireCercle(rayon));
}
}
Il est possible de séparer les fonctions dans différentes classes. Pour appeler une fonction d’une autre classe, il suffit de mettre son nom avant :
class CercleUtil {
static double aireCercle(double rayon) {
return Math.PI*rayon*rayon;
}
static void afficheAireCercle(double rayon) {
System.out.println(aireCercle(rayon));
}
}
class Prog {
public static void main(String[] args) {
double ratyon = 20;
CercleUtil.afficheAireCercle(rayon);
}
}
Mais pourquoi ne pas mettre les fonctions de CercleUtil
dans la classe Cercle
?
Java est fourni avec de nombreuses classes utilitaires. Aujourd’hui, nous avons vu :
System.nanoTime()
qui retourne le nombre de nanosecondes depuis un temps inconnu mais fixeSystem.out.println
qui est la version Java du println
de JavascoolMath.PI
qui contient la valeur π