Création de classes
Introduction
Rappelons qu’une classe est un modèle que le système utilise pour instancier l’objet correspondant en mémoire. Ce sont ces modèles que le développeur déclare dans ce qui est communément appelé les fichiers sources (fichiers d’extension .java) de son projet. C’est ce que nous venons de faire dans l’exercice LabTypesJava avec la classe Main.
Package
Les classes Java sont regroupées par finalités dans des ensembles appelés packages eux-mêmes organisés sous forme hiérarchique. Quand vous rédigez le code source et que vous souhaitez utiliser une classe d’un package donné, il vous faut soit préciser son chemin complet à chaque appel ou, de façon plus concise, déclarer son importation dans l’en-tête du fichier source. Les classes « standard » du package java.lang ne suivent pas cette règle ; elles sont directement accessibles !
Les classes que vous allez développer devront obligatoirement appartenir à des packages que vous allez donc devoir créer. L’organisation de ces ensembles de classes et de leurs hiérarchies vous permet de contrôler la portée des packages, des classes et des méthodes.
Il existe des conventions de nommage pour les packages (comme d’ailleurs pour les classes, les méthodes, etc.). Celles-ci peuvent varier d’une entreprise à l’autre. Généralement, le package commence par le nom d’un type de domaine (com, edu, gov, mil, net, org) suivi par un point (.), suivi par le nom de la société, lui-même suivi par un point (.). Ensuite, il peut y avoir un nom de projet ou de module utilisable dans plusieurs projets, etc.
Exemple de nom de package
com.eni.facturation.exportcompta
Choisissez des noms de package synthétisant le rôle de la famille de classes qu’ils rassemblent.
Généralement, l’analyse UML menée en amont vous aidera pour le nommage. Ce nom doit commencer par une lettre ou par un tiret bas (_). Il peut ensuite contenir des lettres...
Déclaration d’une classe
On a déjà un peu « empiété » sur la théorie, alors ajoutons une classe à notre package.
Sélectionnez le package com.masociete.
Affichez le menu contextuel du clic droit, puis sélectionnez New - Java Class.
Entrez le nom « MaPremiereClasse » puis validez.
L’ossature de la classe a été créée.
Notez la déclaration de son package d’appartenance en toute première ligne.
Il n’est pas possible de déclarer plus d’une classe de type public dans le même fichier source, sauf si elles sont imbriquées ou si les classes sont privées au fichier source qui les contient.
Si vous essayez de déclarer deux classes de type public dans un même fichier source, IntelliJ IDEA réagira comme suit :
Une classe se déclare avec le mot-clé class suivi par le nom que vous lui avez choisi. Comme pour le package, ce nom doit commencer par une lettre ou par un tiret bas (_). Il peut ensuite contenir des lettres, des chiffres et des tirets bas. Évitez d’utiliser les caractères accentués et optez pour une convention de nommage de type Pascal Case. Par exemple, si vous écrivez une classe émulant un lecteur numérique, le nom au format « PascalCase » sera LecteurNumerique. Les premières lettres des mots liés sont en majuscules.
Expliquée en détail plus loin, la déclaration d’un héritage éventuel intervient ensuite. En effet si la classe étend une classe existante, alors le mot-clé extends précède le nom de la superclasse.
Si la classe implémente une ou plusieurs interfaces, alors le mot-clé implements précède la liste des interfaces supportées par la classe.
Les membres de la classe (les attributs et les méthodes) sont ensuite définis entre accolades.
Syntaxe de déclaration
visibilité class NomClass [[extends ClasseMere]
[implements Liste interfaces de base]]
{
// corps de la classe
}
Exemple
package com.masociete;
public class ClasseHeritiere extends SaClasseMere implements
Interface1, Interface2 { ...
Les interfaces
1. Introduction
Expliquer les interfaces et leurs intérêts est toujours plus convaincant avec un exemple concret à l’appui... Alors, imaginons un programme permettant de piloter un système domotique depuis un téléphone portable (avec nos smartphones toujours connectés, l’engouement pour ce type d’application explose !). Ce programme graphique permettra de commander des volets électriques, lire des températures, démarrer un four... Bref, lire et écrire des états logiques (vrai ou faux) et lire et écrire des valeurs analogiques (00 à 255, par exemple).
Dans ce genre d’applications, il faut veiller à ne pas être lié à un matériel précis. Un changement de la carte entrées/sorties - c’est-à-dire la carte qui va lire les capteurs et piloter les relais commandant les équipements - doit impacter le moins possible le code existant.
C’est là que les interfaces de programmation vont nous aider...
2. Le contrat
Pour réussir notre indépendance vis-à-vis du matériel, il faut limiter ses liens à leurs plus simples expressions et les contractualiser.
Par analogie, on peut dire que c’est grâce à la prise au format standard Connecteur Jack 3.5mm stéréo que n’importe quel casque audio peut se connecter à n’importe quel baladeur numérique. Cette fameuse prise joue le rôle d’interface entre deux matériels qui ne sont pas obligatoirement issus du même constructeur.
Limiter les liens à leurs plus simples expressions revient à lister les fonctionnalités minimales attendues pour la carte d’entrées/sorties. Cette liste est une sorte de contrat que devra obligatoirement respecter le matériel. Pour reprendre l’analogie précédente, les fabricants de casques audio proposent des produits avec des connecteurs de liaison aux diamètres normalisés, et c’est par cette interface que l’interconnexion devient possible.
Alors, pour ce projet domotique, quels sont nos besoins ?
Il faut pouvoir :
-
lire des états binaires sur des entrées référencées : interrupteurs, poussoirs, capteurs de présences ;
-
lire...
Associations, compositions et agrégations
Dans tout programme, le développeur est amené à concevoir des classes qui utilisent ou contiennent d’autres classes pouvant à leur tour utiliser ou contenir d’autres classes, etc. Par exemple, un formulaire (boîte de dialogue avec l’utilisateur) affiche différents contrôles comme des boutons radio, des cases à cocher, des boîtes de saisie de texte ou autres listes déroulantes. Le formulaire et chacun de ses contrôles sont encapsulés dans des classes que le développeur va associer pour parvenir à l’affichage de la boîte finale.
Les associations sont plus ou moins fortes. Dans notre exemple l’association est forte car c’est le formulaire qui instancie ces contrôles et ces mêmes contrôles seront détruits à sa fermeture. On parle alors d’agrégation composite ou plus simplement de composition.
Pendant cette association, l’objet Contenant accède librement aux membres de type public de chacun des objets Contenu. Ainsi, lors de son chargement, notre formulaire pourra initialiser les contenus par défaut des boîtes texte, les sélections des boutons radio et, au moment de la validation, récupérer les choix de l’utilisateur en interrogeant chacun des contrôles.
Comment le langage Java nous permet-il de gérer ces différentes formes de collaboration ?
Quel que soit le degré d’association, la classe Contenant aura besoin de stocker des références sur les classes contenues.
Il peut y avoir plusieurs objets de mêmes types référencés dans la classe Contenant. Cette pluralité s’exprime d’ailleurs en UML par un indice au bout de la liaison indiquant soit une quantité finie soit une fourchette possible.
Dans cet exemple, un auteur peut écrire un ou un nombre indéfini d’ouvrages (1..*) et un ouvrage n’est rattaché qu’à un seul auteur (1).
Le langage Java va donc avoir plusieurs formes de codages pour ces différents types d’association.
-
La classe Contenant contient une simple référence à un objet de type Contenu.
class Contenant{
Contenu contenu = null;
}
Traduction...
Les classes imbriquées
Il est possible de déclarer une classe... dans une classe ! Cette fonctionnalité offre au développeur une façon supplémentaire d’organiser son code. Les classes principales sont enregistrées dans des packages et il est donc possible d’effectuer des regroupements à l’intérieur de ces classes principales.
La plupart du temps, la classe imbriquée - appelée nested class ou inner class - n’a aucune signification en dehors de sa classe hôte et son opérateur de visibilité est de type private. Il est malgré tout possible de modifier ce type d’accès en public, protected ou package private.
Syntaxe d’une nested class
public class ClasseHote {
class ClasseImbriquee {
}
//...
}
La classe imbriquée a accès à tous les membres de la classe hôte. La classe hôte a accès à tous les membres de la classe imbriquée.
Le fait de déclarer une classe dans une autre n’implique absolument pas une quelconque instanciation automatique de la classe imbriquée au moment de l’instanciation de la classe hôte. Cela reste une déclaration.
La classe imbriquée peut être de type static et ,dans ce cas, est communément appelée static nested class.
Si la classe imbriquée n’est pas de type static, elle est appelée inner class.
Exemple de codage d’une classe imbriquée et de sa classe hôte
package com.eni;
public class Main {
public static void main(String[] args) {
ClasseHote ch = new ClasseHote();
ch.TestNestedClass(); ...
Quelques différences avec le C#
Les langages Java et C# sont très proches. Ce sont tous les deux des langages de références (au sens propre comme au sens figuré) et le passage de l’un à l’autre ne posera pas de gros problèmes.
Si vous êtes développeur C#, les différences à savoir concernant ce chapitre sont que Java ne supporte pas :
-
Les structures : héritage du C, les structures sont très proches des classes en C#. La principale différence concerne la mémoire dans laquelle elles sont stockées. Les classes sont écrites sur le tas (heap) et les structures sont sur la pile (stack) donc très rapides d’accès car déclarées par le compilateur, elles ne nécessitent aucune allocation du système d’exploitation.
-
Les classes partielles (définies dans plusieurs fichiers sources) : c’est un bien, diront certains. Cette fonctionnalité, pourtant très pratique, fait débat.
-
Les méthodes partielles qui ont leurs signatures définies dans un fichier source et leurs implémentations (optionnelles) définies dans un autre : même remarque.
-
La surcharge d’opérateurs et notamment les [] représentant des indexeurs ou encore le == permettant de tester l’égalité entre deux objets. Il est vrai...