1. Livres & vidéos
  2. Java
  3. Programmation objet
Extrait - Java Les fondamentaux du langage (avec exercices pratiques et corrigés) (2e édition)
Extraits du livre
Java Les fondamentaux du langage (avec exercices pratiques et corrigés) (2e édition) Revenir à la page d'achat du livre

Programmation objet

Introduction à la POO

Avec Java, la notion d’objet est omniprésente et nécessite un minimum d’apprentissage. Nous allons donc voir dans un premier temps les principes de la programmation objet et le vocabulaire associé, puis nous verrons comment mettre cela en application avec Java.

Dans un langage procédural classique, le fonctionnement d’une application est réglé par une succession d’appels aux différentes procédures et fonctions disponibles dans le code. Ces procédures et fonctions sont chargées de manipuler les données de l’application qui sont représentées par les variables de l’application. Il n’y a aucun lien entre les données et le code qui les manipule. Dans un langage objet, on va au contraire essayer de regrouper le code. Ce regroupement est appelé une classe. Une application développée avec un langage objet est donc constituée de nombreuses classes représentant les différents éléments manipulés par l’application. Les classes vont décrire les caractéristiques de chacun des éléments. C’est ensuite l’assemblage de ces éléments qui va permettre le fonctionnement de l’application.

Ce principe est largement utilisé dans d’autres domaines que l’informatique. Dans l’industrie automobile, par exemple, il n’existe certainement pas, chez aucun constructeur, un plan complet décrivant...

Mise en œuvre de la POO avec Java

1. Contexte

Dans le reste de ce chapitre, nous allons travailler sur la classe Person et ses sous-classes. Cette classe Person représentera une personne au sens courant du terme en français. Notez qu’il est généralement recommandé de nommer ses classes en anglais, et nous respecterons cette règle. Vous trouverez ci-dessous une représentation simplifiée en UML (Unified Modeling Language) de cette hiérarchie de classes.

images/03RI01N1.png

UML est un langage graphique dédié à la représentation des concepts de programmation orientée objet. Pour plus d’informations sur ce langage, vous pouvez consulter l’ouvrage UML 2.5 dans la collection Ressources Informatiques des Éditions ENI.

En UML, une flèche triangulaire vide décrit une dérivation. La classe dérivée est la classe de base, mais avec des propriétés additionnelles ou modifiées, comme le concept d’héritage présenté précédemment.

Nous avons la classe Person qui hérite de la classe Object (comme toute classe en Java). Cette classe Person possédera deux classes filles : Customer (un client) et Provider (un fournisseur).

2. Création d’une classe

La création d’une classe passe par la déclaration de la classe elle-même et de tous les éléments la constituant.

a. Déclaration de la classe

La déclaration d’une classe se fait en utilisant le mot-clé class suivi du nom de la classe puis d’un bloc de code délimité par les caractères { et }. Dans ce bloc de code, on trouve des déclarations de variables qui seront les champs de la classe et des fonctions qui seront les méthodes de la classe. Plusieurs mots-clés peuvent être ajoutés pour modifier les caractéristiques de la classe. La syntaxe générale de déclaration d’une classe est donc la suivante :

[modificateurs] class NomDeLaClasse 
              [extends NomDeLaClasseDeBase] 
              [implements NomDeInterface1,NomDeInterface2, ...] { 
{  ...

Les commentaires

1. Présentation

Les commentaires sont des éléments essentiels d’un code bien écrit. Ils permettent d’expliquer le fonctionnement du code aux développeurs qui le lisent, y compris votre futur vous. Il existe deux écoles concernant les commentaires en code : ceux qui pensent qu’il faut beaucoup commenter et ceux qui disent que si vous avez besoin de commenter, c’est que votre code est mal écrit. La vérité se trouve probablement entre les deux : les commentaires doivent être utilisés pour expliquer des aspects du code qui ne sont pas évidents, mais un code bien structuré et lisible peut souvent se passer de nombreux commentaires.

Java propose plusieurs types de commentaires qui peuvent être utilisés pour des objectifs différents. Nous verrons dans ce chapitre les principaux types de commentaires disponibles dans un programme Java.

2. Commentaires sur une ligne

Les commentaires sur une ligne commencent par // et se poursuivent jusqu’à la fin de la ligne. Ils sont utilisés pour ajouter des explications simples ou temporaires au code. Voici un exemple :

int number = 5; // Déclare une variable nommée et l'initialise à 5 

Ce type de commentaire est utile pour ajouter des explications concises sur une ligne de code spécifique.

3. Commentaires sur plusieurs lignes

Les commentaires...

Les packages

1. Présentation

Le but principal des packages est l’organisation et le rangement des classes et interfaces d’une application. Le principe est le même que dans la vie courante. Si vous avez un grand placard dans votre maison, il est évident que l’installation d’étagères dans ce placard va faciliter l’organisation et la recherche des objets qui y sont rangés par rapport à un stockage en « vrac ». L’organisation des classes en packages apporte les avantages suivants :

  • Facilité pour retrouver et réutiliser un élément.

  • Limitation des risques de conflits de noms.

  • Création d’une nouvelle visibilité (la visibilité package) en plus des visibilités standards (private, protected, public). Lorsqu’un élément (une classe, un champ, une méthode) ne présente pas de modificateurs de visibilité, alors cet élément n’est visible que par les éléments présents dans le même package. Il existe une subtilité pour les éléments protégés (champs, méthodes). Ils sont bien sûr accessibles dans les classes dérivées, mais aussi dans les classes du même package.

2. Création d’un package

La première chose à faire lorsque l’on souhaite créer un package est de lui trouver un nom. Ce nom doit être choisi avec précaution pour permettre au package de remplir pleinement son rôle. Il doit être unique et représentatif des éléments stockés à l’intérieur.

Pour assurer l’unicité du nom d’un package, on utilise par convention le nom de domaine de l’entreprise en inversant l’ordre des éléments comme première partie pour le nom du package.

Par exemple, si le nom de domaine est eni.fr, la première partie du nom du package sera fr.eni. Si le nom de domaine comporte des caractères interdits pour les noms de packages, ils sont remplacés par le caractère _. Ainsi pour le nom de domaine eni-ecole.fr, la première partie du nom de package sera fr.eni_ecole. Cette première partie du nom permet d’assurer l’unicité des éléments...

Les modules

1. Mise en place

Lors de la création d’un projet Java avec Eclipse, la fenêtre suivante apparaît pour demander la création d’un fichier nommé module-info.java :

images/03RI28N.png

Cette étape est liée à l’apparition de la notion de modules avec Java 9. Cliquez sur Create et un fichier module-info.java est créé dans le répertoire racine contenant les sources.

Dans intelIiJ, la création du fichier module-info.java n’est pas suggérée, mais sera nécessaire après avoir créé un module : faites un clic droit à la racine du projet et sélectionnez New puis Module.

Le contenu de ce fichier est le suivant :

module Projet_JavaSE_Chapitre3_Modules { 
} 

2. Présentation

Depuis Java 9, l’API Java a été modularisée. L’objectif est multiple. Le principal intérêt de la modularisation de l’API est le gain en performance lors de l’exécution des programmes. En effet, la modularisation permet à la machine virtuelle de ne charger que les modules nécessaires au bon fonctionnement des programmes qu’elle exécute.

Ceci peut sembler superflu avec les capacités de calcul des machines actuelles, mais ceci prend tout son sens si l’on se penche sur l’Internet des objets (IOT - Internet Of Things), c’est-à-dire les programmes embarqués dans des objets...

La gestion des erreurs

Dans la vie d’un développeur, tout n’est pas rose ! Les erreurs sont une des principales sources de stress. En fait, lorsque l’on y regarde de plus près, nous pouvons classer ces erreurs qui nous gâchent la vie en trois catégories. Regardons chacune d’entre elles et les solutions disponibles pour les traiter.

1. Les différents types d’erreurs

a. Les erreurs de syntaxe

Ce type d’erreur se produit au moment de la compilation lorsque le développeur fait une faute de frappe. Très fréquentes avec les outils de développement où l’éditeur de code et le compilateur sont deux entités séparées, elles deviennent de plus en plus rares avec les environnements de développement intégré (IntelliJ, Eclipse). La plupart de ces environnements proposent une analyse syntaxique au fur et à mesure de la saisie du code.

Si une erreur de syntaxe est détectée, alors l’environnement propose des solutions possibles pour corriger cette erreur.

images/03RI13N1.png

D’autre part, les « fautes d’orthographe » dans les noms de champs ou de méthodes sont facilement éliminées grâce aux fonctionnalités disponibles dans ces environnements.

b. Les erreurs d’exécution

Ces erreurs apparaissent après la compilation lorsque vous lancez l’exécution de votre application. La syntaxe du code est correcte, mais l’environnement de votre application ne permet pas l’exécution d’une instruction utilisée dans votre application. C’est par exemple le cas si vous essayez d’ouvrir un fichier qui n’existe pas sur le disque de votre machine. Vous obtiendrez sûrement un message de ce type :

images/03RI36.png

Ce type de message peut dérouter l’utilisateur et nuire à l’expérience globale. Heureusement, Java offre des mécanismes pour intercepter ces erreurs, évitant ainsi l’affichage de messages brutaux ou cryptiques. Ces mécanismes seront détaillés plus loin dans ce chapitre (cf. section La gestion des erreurs).

c. Les erreurs de logique

Ce sont les pires ennemies des développeurs. Tout se compile sans problème, tout s’exécute sans erreurs et pourtant, ça ne marche...

Les génériques

1. Présentation

Les types génériques sont des éléments d’un programme qui s’adaptent automatiquement pour réaliser la même fonctionnalité sur différents types de données. Lorsque vous créez un élément générique, vous n’avez pas besoin de concevoir une version différente pour chaque type de donnée avec lequel vous souhaitez réaliser une fonctionnalité. Pour faire une analogie avec un objet courant, nous allons prendre l’exemple d’un tournevis. En fonction du type de vis que vous avez à utiliser, vous pouvez prendre un tournevis spécifique pour ce type de vis (plat, cruciforme, torx...). Une technique fréquemment utilisée par un bricoleur averti consiste à acquérir un tournevis universel avec de multiples embouts. En fonction du type de vis, il choisit l’embout adapté. Le résultat final est le même que s’il disposait d’une multitude de tournevis différents : il peut visser et dévisser.

Lorsque vous utilisez un type générique, vous le paramétrez avec un type de données. Ceci permet au code de s’adapter automatiquement et de réaliser la même action indépendamment du type de données. Une alternative pourrait être l’utilisation du type universel Object. L’utilisation des types génériques présente plusieurs avantages par rapport à cette solution :

  • Elle impose la vérification des types de données au moment de la compilation et évite les inévitables vérifications qui doivent être faites manuellement avec l’utilisation du type Object.

  • Elle évite les opérations de conversion du type Object vers un type plus spécifique et inversement.

  • L’écriture du code est facilitée dans certains environnements de développement avec l’affichage automatique de tous les membres disponibles pour un type de données particulier.

  • Elle favorise l’écriture d’algorithmes qui sont indépendants des types de données. 

Les types génériques peuvent cependant imposer certaines restrictions concernant le type de données utilisé....

Les collections

1. Présentation

Les applications ont très fréquemment besoin de manipuler de grandes quantités d’informations. De nombreuses structures sont disponibles pour faciliter la gestion de ces informations. Elles sont regroupées sous le terme collection. Comme dans la vie courante, il y a différents types de collections et de collectionneurs. Il peut y avoir des personnes qui récupèrent toutes sortes de choses, mais qui n’ont pas d’organisation particulière pour les ranger, d’autres qui sont spécialisées dans la collection de certains types d’objets, les maniaques qui prennent toutes les précautions possibles pour pouvoir retrouver à coup sûr un objet...

Le langage Java propose différentes classes permettant de mettre en place plusieurs modes de gestion.

La première solution pour gérer un ensemble d’éléments est l’utilisation de tableaux. Cette solution a été décrite dans la section Les tableaux du chapitre Comprendre un programme. Bien que simple à mettre en œuvre, cette solution n’est pas très souple. Son principal défaut tient au caractère fixe de la taille d’un tableau. S’il n’y a plus de place pour stocker des éléments supplémentaires, il faut créer un nouveau tableau plus grand et y transférer le contenu du tableau précédent. Cette solution, lourde à mettre en œuvre, est consommatrice de ressources.

Le langage Java propose une vaste palette d’interfaces et de classes pour gérer facilement des ensembles d’éléments. Les interfaces décrivent les fonctionnalités disponibles alors que les classes implémentent et fournissent réellement ces fonctionnalités. Suivant le mode de gestion des éléments souhaité, on utilisera bien sûr la classe la plus adaptée. Cependant, les mêmes fonctionnalités de base doivent être accessibles, quel que soit le mode de gestion. Pour assurer la présence de ces fonctionnalités indispensables dans toutes les classes, celles-ci ont été regroupées dans l’interface Collection qui est par la suite utilisée comme interface de base.

La hiérarchie...

Les entrées/sorties (I/O)

1. Introduction

Les entrées/sorties sont un élément essentiel en informatique, permettant aux programmes de communiquer avec l’utilisateur, de lire et d’écrire des données sur des périphériques de stockage, de transmettre des données sur un réseau, etc. En Java, les entrées/sorties sont gérées à l’aide de flux (stream en anglais), qui permettent de lire et d’écrire des données de manière séquentielle.

Dans ce chapitre, nous allons explorer les fondamentaux des entrées/sorties en Java. Tout d’abord, nous allons définir les entrées/sorties et expliquer leur importance. Ensuite, nous verrons comment les flux fonctionnent et comment les utiliser pour lire et écrire des données à partir de différentes sources, telles que des fichiers et des consoles. Nous verrons également comment sérialiser et désérialiser des objets en Java, ce qui permet de stocker et de transmettre des objets de manière efficace.

Les problématiques d’entrées/sorties dans un programme ne sont pas spécifiques à la POO et peuvent être rencontrées dans des contextes de programmation procédurale. Cependant, il est essentiel d’avoir vu les précédentes explications sur la POO pour mieux comprendre les exemples qui vont suivre.

Définition des entrées/sorties

Les entrées/sorties (E/S ou I/O en anglais pour input/output) sont le processus par lequel un programme informatique interagit avec son environnement. Les entrées font référence aux données que le programme reçoit de l’extérieur, tandis que les sorties font référence aux données que le programme envoie à l’extérieur. Les entrées/sorties peuvent impliquer des périphériques tels que des claviers, des écrans, des imprimantes, des disques durs, des bases de données, des réseaux, etc.

2. Les flux d’entrée et de sortie en Java

Les flux sont des objets qui permettent de gérer l’envoi et la réception de données de manière séquentielle. En Java, les flux sont classés en plusieurs catégories...

Exercices

1. Exercice 1

Créer une classe représentant un article d’un magasin de vente par correspondance. Un article est caractérisé par sa référence, sa désignation, son prix. Créer ensuite une méthode main permettant de tester le bon fonctionnement de la classe précédente.

2. Exercice 2

Ajouter les deux classes Book et BluRay héritant de la classe Article.

Un livre possède un numéro ISBN, contient un certain nombre de pages et a été écrit par un auteur, un Blu-Ray a une certaine durée et a été produit par un réalisateur.

Ajouter les attributs nécessaires aux classes Book et BluRay pour avoir le nom de l’auteur ou du réalisateur. Tester ensuite le fonctionnement de ces deux nouvelles classes.

3. Exercice 3

Modifier les classes Book et BluRay pour pourvoir disposer des informations suivantes concernant l’auteur ou le réalisateur :

  • son nom ;

  • son prénom ;

  • sa date de naissance.

Indice : les auteurs et les réalisateurs sont des personnes.

4. Exercice 4

Modifier le code précédent pour pouvoir obtenir rapidement la liste des articles concernant un auteur ou un réalisateur.

Corrections

1. Correction de l’exercice 1

La classe Article est relativement simple. Les lignes 2 à 4 contiennent la déclaration des attributs représentant les informations que l’on souhaite mémoriser dans les instances de la classe. Ils sont déclarés avec la visibilité private afin que seul le code de la classe puisse y accéder. Viennent ensuite les constructeurs (lignes 6 à 10 et 12 à 13), qui permettent la création d’instances selon les informations disponibles.

Bien qu’il soit possible d’accéder directement aux attributs dans les constructeurs, il est préférable d’utiliser les méthodes setXXX pour profiter des éventuels contrôles sur les valeurs affectées aux attributs.

Pour rendre les attributs accessibles depuis l’extérieur de la classe, chacun doit disposer d’une méthode getXXX et setXXX afin de récupérer ou modifier sa valeur. La méthode toString (lignes 39 à 41) fournit quant à elle une représentation textuelle de l’instance.

Dans la classe Main, la méthode main (lignes 2 à 15) crée deux instances de la classe Article :

  • en utilisant d’abord le constructeur par défaut (lignes 4 à 7), puis en initialisant chaque attribut ;

  • ou directement en appelant le constructeur avec paramètres (ligne 9).

Les deux instances sont ensuite affichées, soit via l’appel à la fonction test (lignes 17 à 21), qui définit un format d’affichage précis, soit en invoquant directement la méthode toString. Dans ce dernier cas, le formatage est celui défini dans la classe Article.

/********************* Article **********************/ 
1. public class Article { 
2.    private int reference; 
3.    private String designation; 
4.    private double price; 
5. 
6.    public Article(int reference, String designation, double price) { 
7.        this.reference = reference; 
8.        this.designation = designation; 
9.        this.price = price; 
10.    } ...