Capsule de théorie Notion de classe

Introduction

En programmation orientée objet, la notion de classe recouvre plusieurs aspects :

  • une unité d’organisation du code
  • un truc pour construire des objets.
  • un type de donnée abstrait

Unité d’organisation du code

Une classe permet de regrouper dans un même fichier une structure de données et les sous‑programmes (procédures et fonctions) qui permettent de manipuler cette structure de données. Les éléments de la structure de données sont appelés attributs, champs (fields) ou variables membres ; les sous‑programmes, quant à eux, sont appelés méthodes (methods ou features). Les attributs et les méthodes d’une classe sont appelés collectivement « membres » de cette classe.

Il existe deux sortes de membres :

  • Les attributs et méthodes de classe.
  • Les attributs et méthodes d’instance.

Les attributs et les méthodes de classe sont la version orientée objet des variables globales et des sous‑programmes des langages procéduraux. La mémoire occupée par les variables et les constantes est allouée au démarrage du programme et n’est libérée que lorsque le programme s’arrête. Une méthode de classe est un sous‑programme au sens de la programmation procédurale et peut être appelée partout où la classe est visible. Pour une méthode de classe, la classe n’est rien d’autre qu’un espace de noms.

En Java, les membres de classe sont appelés membres statiques. Pour déclarer un membre statique, on utilise le modificateur static après le modificateur de visibilité (public ou private). Le nom complet d’une variable ou d’une méthode statique est composé du nom du package, du nom de la classe et du nom du sous‑programme séparé par des points. Pour éviter d’avoir à écrire chaque fois le nom complet, on peut utiliser des clauses import. Cette clause permet d’importer dans un fichier, un symbole défini dans un autre fichier. Les variables et les fonctions globales de la bibliothèque standard de Java sont des membres statiques. Par exemple :

  • Les variables globales java.lang.System.out ou java.lang.System.in
  • La fonction java.util.Arrays.copyof
  • La constante globale java.lang.Integer.MAX_VALUE

Les membres statiques sont utiles, mais on évite autant que possible d’y avoir recours. La programmation orientée objet repose d’abord et avant tout sur la notion d’objet, c’est-à-dire d’instance d’une classe.

Construction d’objet (instanciation et membres d’instances)

L’instanciation est l’opération qui permet de créer une nouvelle instance d’une classe ou, en d’autres termes, de créer un nouvel objet. Cette opération consiste essentiellement à allouer dans le tas (heap) la mémoire nécessaire au stockage des valeurs des variables membres définies par la classe et à initialiser ces variables. Chaque instance possède sa propre zone de mémoire identifiée par un nombre entier que l’on appelle référence (il s’agit généralement de l’adresse du premier octet de la zone de mémoire).

En Java, on crée un nouvel objet à l’aide d’une expression d’instanciation. Une telle expression est composée de l’opérateur new suivi du nom du constructeur de la classe, qui a le même nom que la classe elle-même, et de la liste de ses paramètres effectifs entre parenthèses (éventuellement vides). Un constructeur est une méthode particulière d’une classe. Il n’a pas de type de retour, pas même void, et doit obligatoirement avoir le même nom que la classe (il commence donc par une majuscule). Le rôle d’un constructeur est d’initialiser toutes les variables membres du nouvel objet. L’opération d’instanciation est une opération avec effet de bord (un nouvel objet est créé à chaque fois que l’opération est évaluée), c’est pourquoi on ne la trouve généralement que dans la partie droite d’une affectation.

Dans l’exemple ci-après, après l’exécution du code, on a trois variables (date1, date2 et date3) de type Date, mais seulement deux objets ; date2 et date3 se réfèrent au même objet. En effet, dans l’expression de la partie droite des deux premières affectations est une opération d’instanciation (new Date()). La variable date1 contient la référence du premier objet et la variable date2 la référence du second. En revanche, l’expression de la partie droite de la troisième affectation est la variable date2, l’effet est donc de stocker la valeur de date2 dans date3. Or, la valeur de date2 est la référence du second objet ; de fait, après l’affectation, la variable date3 contient également la référence du second objet.

1
2
3
4
5
6
7
8
9
    ...
    Date date1;
    Date date2;
    Date date3;

    date1 = new Date();
    date2 = new Date();
    date3 = date2;
    ...
Fig. 1 – Opération d’instanciation

Une fois qu’un objet est instancié, on peut utiliser n’importe quelle expression dont la valeur est la référence de cet objet (p.ex. une variable) pour lui appliquer des opérations. Les opérations qu’il est possible d’appliquer aux instances d’une classe sont les méthodes d’instance publiques définie dans cette classe. Pour appliquer une opération à un objet en Java, on utilise la syntaxe .<nomDeLaMéthode>(<params>) sur une expression dont la valeur est la référence de cet objet. Dans l’exemple ci-après, on applique l’opération getTime à l’objet référencé par la variable date1. Cette opération a pour effet de renvoyer le nombre de millisecondes qui se sont écoulées entre le 1er janvier 1970, à 0 h, et la date représentée par l’objet.

1
2
3
4
    ...
    Date date1 = new Date();
    long time = date1.getTime();
    ...
Fig. 2 – Appel d’une méthode sur un objet

En Java, un membre est par défaut un membre d’instance. Une méthode d’instance doit être considérée comme une opération que l’on applique sur un objet. La référence de l’objet est donc un paramètre implicite de méthode. En Java, contrairement à Python par exemple, il n’est pas nécessaire de déclarer un paramètre formel pour l’instance, le mot clé this permets d’y accéder. Une bonne pratique consiste à toujours utiliser le mot clé this pour référencer un membre d’instance (attribut ou méthode) dans l’implémentation d’une méthode.

Type

Le dernier aspect est celui de type. Une classe peut être vue comme l’implémentation d’un type de données abstrait (abstract data type ou ADT). En effet, un type abstrait peut être défini comme un ensemble d’opérations. Or, si l’on considère les méthodes d’une classe comme des opérations que l’on peut appliquer à ses instances, alors on peut dire que l’ensemble des méthodes d’instance publiques forme un type. L’ensemble des méthodes d’instance publiques d’une classe est parfois appelé l’interface de cette classe.

Il est important de noter qu’une classe n’est pas un type, mais une classe a toujours au moins un type (son interface). C’est pourquoi en Java, le nom d’une classe peut apparaître partout où le nom d’un type est attendu. Enfin, le sous-typage permet à une classe d’avoir plusieurs types.