1. Introduction
1.1. Caractéristiques des langages de programmation
1.1.1. Implémentation des langages
-
Avec un langage compilé, le code source du programme est transformé en code machine par le compilateur
-
Dans un langage interprété, le code source du programme est exécuté "à la volée" par l'interpréteur
-
Certains langages sont à la fois compilés et interprétés
-
Il existe des approches intermédiaires (compilation Just In Time (JIT))
1.1.2. C est compilé
util.o et main.o)$ gcc -c util.c
$ gcc -c main.c
monprog)$ gcc -o monprog main.o util.o
monprog est exécutable$ ./monprog
1.1.3. PHP est interprété
$ php monprog.php
1.1.4. Java est compilé puis interprété (JIT)
$ javac Main.java
$ java Main
1.1.5. Langage de scripts
-
Un script est un programme destiné à automatiser l’enchaînement de tâches dans un environnement particulier
-
Un langage de scripts est un langage de programmation permettant de développer des scripts
-
Il permet d’invoquer les primitives du système sous-jacent
-
Il dispose en général d’un REPL (Read-Eval-Print Loop)
-
Quelques exemples
-
shells pour les OS : Bash, Zsh, tcsh
-
ECMAScript (Javascript) pour les navigateurs web
-
Lua embarqué dans une application (VLC Media Player, jeu Battle for Wesnoth)
-
1.1.6. Système de typage
-
Un système de typage attribue des types aux éléments du langage
-
Attribuer un type à une expression permet de limiter les erreurs de programmation
-
en définissant ce qu’il est possible de faire avec une expression
-
en définissant les règles de compatibilité entre expressions
-
en vérifiant ces contraintes
-
1.1.7. Typage explicite vs. implicite
-
Le typage est explicite si les annotations de type sont visibles dans le code source
int nombre = 1;
double pi = 3.141592;
-
Le typage est implicite si les types ne sont pas précisés dans le code source
$nombre = 1;
$pi = 3.141592;
-
Des langages à typage explicite peuvent faire appel à l'inférence de types dans certaines situations
-
permet la déduction automatique des types
-
1.1.8. Typage statique vs. dynamique
-
Le typage est statique si l’information de type est associée à l’identificateur
-
⇒ la vérification des types peut être réalisée lors de la compilation
-
-
Le typage est dynamique si l’information de type est portée par l’objet lui-même
-
⇒ la vérification se fait durant l’exécution
-
1.1.9. Typage statique
-
Améliore la fiabilité du programme (plus de vérifications plus précoces)
-
Meilleur support des outils (IDE)
-
Meilleures performances
double a = "une chaine";
// error: incompatible types when initializing type ‘double’ using type ‘char *’
1.1.10. Typage dynamique
-
Offre plus de souplesse dans l’écriture du code source
-
duck typing, data as code, métaprogrammation
-
-
Permet le prototypage rapide
$a = 1;
$a = "une chaine";
echo $a + 2; // affiche 2
1.1.11. Typage fort vs. faible
-
Le typage est fort si les manipulations entre données de types différents sont limitées et contrôlées
-
Le typage est faible si les possibilités de transtypage sont nombreuses et implicites
-
Ces notions sont relativement floues
int a = "une chaine";
printf("%d\n", a); // 443215...
1.1.12. Support des paradigmes de programmation
-
Un paradigme de programmation représente la façon d’aborder un problème et d’en concevoir la solution.
-
Quelques paradigmes
-
Programmation impérative
-
Programmation structurée
-
Programmation modulaire
-
Programmation par abstraction de données
-
Programmation objet
-
-
Programmation fonctionnelle
-
Programmation logique
-
-
Un langage supporte un paradigme quand il fournit les fonctionnalités pour utiliser ce style (de façon simple, sécurisée et efficace)
1.1.13. Exemple - Programmation logique avec Prolog
-
Prolog permet de définir et d’interroger une base de faits
-
Prolog est un langage déclaratif
-
Un fait est une assertion simple
- Idéfix est un chien
chien(idefix).
-
Une règle décrit une inférence à partir des faits
- Les chiens aiment les arbres
aimeLesArbres(X):- chien(X).
-
Une requête est une question sur la base de connaissance
- Idéfix aime-t'il les arbres ?
?- aimeLesArbres(idefix)
1.1.14. Solveur de Sudoku \$4 xx 4\$ en Prolog - Requête
- requête
| ?- sudoku([_, _, 2, 3,
_, _, _, _,
_, _, _, _,
3, 4, _, _],
Solution).
1.1.15. Solveur de Sudoku \$4 xx 4\$ en Prolog - Résolution 1/3
- la solution doit être unifiée avec le problème
- le problème comporte 16 chiffres
- chaque chiffre est compris entre 1 et 4 (fd_domain)
sudoku(Puzzle, Solution) :-
Solution = Puzzle,
Puzzle = [A1, A2, A3, A4,
B1, B2, B3, B4,
C1, C2, C3, C4,
D1, D2, D3, D4],
fd_domain(Puzzle, 1, 4),
1.1.16. Solveur de Sudoku \$4 xx 4\$ en Prolog - Résolution 2/3
- les blocs (lignes, colonnes et carrés) sont définis
Row1 = [A1, A2, A3, A4],
Row2 = [B1, B2, B3, B4],
Row3 = [C1, C2, C3, C4],
Row4 = [D1, D2, D3, D4],
Col1 = [A1, B1, C1, D1],
Col2 = [A2, B2, C2, D2],
Col3 = [A3, B3, C3, D3],
Col4 = [A4, B4, C4, D4],
Square1 = [A1, A2, B1, B2],
Square2 = [A3, A4, B3, B4],
Square3 = [C1, C2, D1, D2],
Square4 = [C3, C4, D3, D4],
1.1.17. Solveur de Sudoku \$4 xx 4\$ en Prolog - Résolution 3/3
- le prédicat valid reçoit une liste de 12 blocs
- la liste vide est valide
- la tête de la liste ne comporte pas de doublons (fd_all_different)
- le reste de la liste doit être valide
valid([]).
valid([Head|Tail]) :-
fd_all_different(Head),
valid(Tail).
- une solution possède des blocs valides
valid([Row1, Row2, Row3, Row4,
Col1, Col2, Col3, Col4,
Square1, Square2, Square3, Square4]).
1.1.18. Exemple - Programmation fonctionnelle avec Haskell
-
Haskell est un langage fonctionnel
-
Possède un système de typage statique, fort et principalement implicite (inférence de types)
1.1.19. Haskell 1/2
-- Calcul de la fonction factorielle
-- Récursive
fact x = if x == 0 then 1 else fact (x - 1) * x
-- Pattern matching
fact 0 = 1
fact x = x * fact (x - 1)
-- Gardes
fact x
| x > 1 = x * fact (x - 1)
| otherwise = 1
-- Liste et intervalle
fac x = product [1..x]
1.1.20. Haskell 2/2
-- Fonctions d'ordre supérieure
mapList f [] = []
mapList f (x:xs) = f x : mapList f xs
-- Listes en compréhension et évaluation paresseuse
take 10 [ (i,j) | i <- [1..], j <- [1..], i < j ]
1.1.21. Langage impératif
-
Un langage impératif représente un programme comme une séquence d’instructions qui modifient son état au cours de son exécution
-
Un programme décrit comment aboutir à la solution du problème
-
Proche de l’architecture matérielle des ordinateurs (architecture de von Neumann)
-
De nombreux langages populaires sont de ce type (C, Java, Python)
1.1.22. Langage déclaratif
-
Un langage déclaratif permet de décrire ce que le programme doit faire (le quoi) et non pas comment il doit le faire (le comment)
-
Un programme respectant ce style décrit le problème à traiter
-
Quelques exemples : Prolog, SQL
-
Certains langages impératifs embarquent des constructions déclaratives
1.1.23. Gestion de la mémoire
-
La gestion de la mémoire dans un langage de programmation décrit comment les objets inutilisés sont identifiés et désalloués
-
nécessaire pour éviter les fuites de mémoire (memory leaks)
-
-
La plupart des langages ont une gestion automatique de la mémoire et s’appuient sur un ramasse-miettes (garbage collector)
int[] tableau = new int[10]; // allocation d'un tableau de 10 cases
// la désallocation est automatique
-
Les langages C et C++ ont une gestion manuelle de la mémoire
int[] tableau = malloc(10 * sizeof(int)); // allocation d'un tableau de 10 cases
// ...
free(tableau); // libération de la mémoire
1.1.24. Caractéristiques de quelques langages
| Langage | Implémentation | Scripts | Typage | Paradigme | Mémoire |
|---|---|---|---|---|---|
C |
Compilé |
Non |
explicite, statique |
procédural |
manuelle |
Java |
Compilé, interprété |
Non |
explicite, statique |
OO, fonc., générique |
auto |
PHP |
Interprété |
Oui |
implicite, dynamique |
proc., OO |
auto |
Python |
Compilé, Interprété |
Oui |
implicite, dynamique |
proc., OO, fonc. |
auto |
Scala |
Compilé, interprété |
Oui |
implicite, statique |
OO, fonc., générique |
auto |
1.2. Quelques langages de programmation
1.2.1. Les langages "historiques"
-
Fortran, John Backus (1954)
-
Lisp, John McCarthy (1958)
-
COBOL, Short Range Committee (1959)
-
Simula, Ole-Johan Dahl et Kristen Nygaard (années 1960)
-
SmallTalk, Alan Kay et Dan Ingalls (début des années 1970)
-
C, Denis Ritchie (1972)
-
Eiffel, Bertrand Meyer (1985)
-
C++, Bjarne Stroustrup (années 1980, Standard ISO en 1998)
1.2.2. Les principaux langages actuels I
1.2.3. Les principaux langages actuels II
1.2.4. Les principaux langages actuels III
1.2.5. Les principaux langages actuels IV
-
Python, Guido van Rossum (1990)
-
PHP, Rasmus Lerdorf (1994)
-
Java, James Gosling (1995)
-
Javascript, Brendan Eich (1995)
-
C#, Anders Hejlsberg (2001)
1.2.6. Les langages "tendances"
-
Go, Robert Griesemer, Rob Pike, Ken Thompson (2009)
-
Groovy, Java Community Process (2003)
-
Julia, Jeff Bezanson, Stefan Karpinski, Viral B. Shah, Alan Edelman (2012)
-
Kotlin, JetBrains (2011)
-
R, Ross Ihaka, Robert Gentleman (1993)
-
Rust, Graydon Hoare (2010)
-
Scala, Martin Odersky (2003)
-
Swift, Apple (2014)
1.2.7. Critères de choix d’un langage
-
Adéquation aux besoins
-
domaine d’application, plateforme cible
-
-
Simplicité d’apprentissage/lisibilité du code source
-
"Programs must be written for people to read, and only incidentally for machines to execute", Harold Abelson
-
"Any fool can write code that a computer can understand. Good programmers write code that humans can understand.", Martin Fowler
-
"Perl – The only language that looks the same before and after RSA encryption", Keith Bostic
-
-
Richesse de la bibliothèque standard
-
Écosystème (bibliothèques tierces, outils)
-
Popularité (support)
-
Employabilité
1.3. Évolution des langages de programmation impératifs
1.3.1. Programmation structurée
| Choisissez les procédures. Utiliser les meilleurs algorithmes que vous pourrez trouver. |
-
L’accent est mis sur les traitements
-
Approche très utilisée depuis de nombreuses années
-
Approche de haut en bas (top-down)
-
A beaucoup amélioré la qualité du logiciel
|
Les données et les traitements restent indépendants.
|
1.3.2. Programmation modulaire
| Choisissez vos modules. Découpez le programme de telle sorte que les données soient masquées par ces modules. |
-
Un module est un ensemble de procédures connexes avec les données qu’elles manipulent
-
L’élément primordial passe de la conception des procédures à l’organisation des données
-
Principe de masquage de l’information
-
Permet la compilation séparée
|
Les types ainsi définis diffèrent dans leur utilisation (création, …) des types de base.
|
1.3.3. Programmation par abstraction de données
| Choisissez les types dont vous avez besoin. Fournissez un ensemble complet d’opérations pour chaque type. |
-
Utilise les types abstraits de données (TAD)
-
L'interface d’un type abstrait isole complètement l’utilisateur des détails d’implémentation
|
Il est nécessaire de modifier un type pour l’adapter. |
1.3.4. Programmation orientée objet
| Choisissez vos classes. Fournissez un ensemble complet d’opérations pour chaque classe. Rendez toute similitude explicite à l’aide de l’héritage. |
-
Consiste à définir les classes puis à préciser les relations entre elles (notamment l'héritage)
-
définir des classes = définir des types utilisateurs
-
factoriser des comportements en créant des hiérarchies d’héritage
-
permet d’adapter un type existant sans le modifier
-
|
La conception objet est une tâche difficile. |
1.3.5. Approche objet vs. approche structurée
-
L’approche objet est moins intuitive
-
décomposer un problème en une hiérarchie de fonctions atomiques et de données est plus naturel
-
-
La modélisation objet est difficile
-
rien dans les concepts de base de l’approche objet ne dicte comment modéliser la structure objet d’un système de manière pertinente.
-
Comment mener une analyse qui respecte les concepts objet ?
-
Sans un cadre méthodologique approprié, la dérive structurée de la conception est inévitable
-
-
L’application des concepts objet nécessite de la rigueur
-
Le vocabulaire précis est un facteur d’incompréhensions
-
1.3.6. Approche objet vs. langage de programmation
-
Certains développeurs ne pensent objet qu’à travers un langage de programmation
-
les langages ne sont que des outils implémentant certains concepts objet d’une certaine façon
-
les langages ne garantissent en rien l’utilisation adéquat de ces moyens techniques
-
|
Programmer en Java, en Python ou en C# n’est pas concevoir objet !
|
1.3.7. Conception objet
| Concevoir objet, c’est d’abord concevoir un modèle qui respecte les concepts objet. |
-
Pour penser et concevoir objet, il faut savoir "prendre de la hauteur", jongler avec des concepts abstraits, indépendants des langages d’implémentation et des contraintes purement techniques
-
Les langages de programmation ne sont pas un support adéquat pour cela
-
Pour conduire une analyse objet cohérente, il ne faut pas directement penser en terme de pointeurs, d’attributs et de tableaux, mais en terme d’association, de propriétés et de cardinalités
1.3.8. Évolutions récentes
-
Concepts OO/modularité : Mixin/ Trait, Délégation, Aspect
-
Concepts issus de la programmation fonctionnelle : fonctions lambda/fermeture (closure), fonction d’ordre supérieure, évaluation parasseuse
-
Programmation parallèle/gestion de la concurrence : support des processeurs multi-cœurs
1.4. Le langage Java
1.4.1. Les plateformes Java
-
Java Platform Standard Edition (Java SE)
-
dédiée aux postes clients et aux stations de travail
-
-
Java Platform Enterprise Edition (Java EE)
-
dédiée aux applications d’entreprises (serveur, postes clients, …)
-
-
Java Embedded
-
dédiée aux systèmes embarqués (Internet des Objets, …)
-
1.4.3. Java Runtime Environment
-
Le Java Runtime Environment (JRE) fournit la machine virtuelle Java, les bibliothèques et d’autres composants nécessaires pour l’exécution de programmes Java
-
Déjà installé sur la plupart des systèmes d’exploitation
-
Le lanceur d’application (
java) est l’outil ligne de commande permettant l’exécution de programme Java
| Plus d’informations : Java Platform Overview |
1.4.4. Java Development Kit
-
Le Java Development Kit (JDK) fournit le JRE ainsi qu’un ensemble d’outils pour le développement d’applications
-
Doit être installé pour développer en Java
-
L’outil
javacest le compilateur en ligne de commande
| Plus d’informations : Java Platform Overview |
1.4.5. Caractéristiques du langage
- Simple
-
un développeur peut être rapidement opérationnel
- Orienté-objet
-
bon respect des concepts OO
- Interprété
-
la compilation génère un code intermédiaire qui est interprété
- Portable
-
un programme compilé fonctionne sans modification sur différentes plateformes
- Robuste
-
vérifications à la compilation et à l’exécution
- Multithread
-
supporte la programmation concurrente
- Adaptable
-
supporte le chargement dynamique de code
- Sécurisé
-
le langage intègre un modèle de sécurité sophistiqué
| The Java Language Environment: A White Paper, James Gosling, 1996. |
1.4.6. Compilation et interprétation
-
Le langage Java est à la fois interprété et compilé
-
Un fichier source (
.java) est compilé en un langage intermédiaire appelé bytecode (.class) -
Ce bytecode est ensuite interprété par la machine virtuelle Java
1.4.7. Compilation en ligne de commande (JDK)
$ javac <options> <fichiers source>
-g|\-g$:$none-
gère les informations pour le débogage
-classpath|-cp-
fixe le chemin de recherche des classes compilées (Classpath)
-source-
précise la version des fichiers sources (
1.6, …,1.8) -sourcepath-
fixe le chemin de recherche des sources
-encoding-
précise l’encodage des fichiers sources ("UTF-8", …)
-d-
fixe le répertoire de destination pour les classes compilées
-target-
précise la version de la VM cible
$ javac -sourcepath src -source 1.7 \
-d classes -classpath classes \
-g src/MonApplication.java
1.4.8. Exécution en ligne de commande (JRE)
$ java [-options] class [args...]
$ java [-options] -jar jarfile [args...]
class-
est ici le nom de la classe (le
.classdoit pouvoir être trouvé dans le CLASSPATH) -cp|-classpath-
fixe le chemin de recherche des classes compilées
-jar-
exécute un programme encapsulé dans un fichier
jar
$ java -cp classes MonApplication
1.4.9. Classpath
-
Le Classpath précise la liste des bibliothèques ou des classes compilées utilisées par l’environnement Java
-
Le compilateur ou la machine virtuelle ont besoin d’avoir accès aux classes compilées
-
Il peut être défini en ligne de commande ou par la variable d’environnement
CLASSPATH
1.4.10. Constructions de base du langage Java
-
Java différencie majuscules et minuscules
-
Java possède une syntaxe proche du C
-
se retrouve à tous les niveaux (commentaires, types, opérateurs, …)
-
chaque instruction se termine par un
;
-
-
Commentaires
/* … */-
le texte entre
/et/est ignoré // …-
le texte jusqu’à la fin de la ligne est ignoré
1.4.11. Types primitifs
Un type primitif est un type de base du langage, i.e. non défini par l’utilisateur. En Java, les valeurs de ces types ne sont pas des objets.
boolean-
trueoufalse byte-
entier de -128 à 127 (les types entiers sont signés)
short-
entier de -32768 à 32767
int-
entier de -231 à 231 - 1
long-
entier de -263 à 263 - 1
char-
caractère Unicode sur 16 bits de
\u0000à\uffff float-
nombre en virgule flottante simple précision (32 bits IEEE 754)
double-
nombre en virgule flottante double précision (64 bits IEEE 754)
1.4.12. Littéraux
Un littéral est la représentation dans le code source d’une valeur d’un type.
- Entiers
-
123de typeint,123Lde typelong,0x123en hexadécimal,0b101en binaire - Flottants
-
1.23E-4de typedouble,1.23E-4Fde typefloat - Booléens
-
trueoufalse - Caractères
-
'a','\t'ou'\u0000' - Chaînes
-
"texte" - Null
-
null(valeur des références non initialisées)
1.4.13. Exemple de déclarations et initialisations de variables
// Exemples de déclaration avec initialisation
// (pas indispensable mais conseillé)
byte aByte = 12; // Un entier sur 8 bits
short aShort = 130; // Un entier sur 16 bits
int anInteger = -153456; // Un entier sur 32 bits
// Remarquer le L pour le litteral de type long
// (sinon erreur a la compilation: entier trop grand)
long aLong = 987654321234L; // Un entier sur 64 bits
// Remarquer le F pour le litteral de type float
// (sinon erreur a la compilation: perte de precision)
float aFloat = 1.3F; // Un reel simple precision
double aDouble = -1.5E-4; // Un reel double precision
char aChar = 'S'; // Un caractere
boolean aBoolean = true; // Un booleen
// La constante est introduite par le mot-cle final
final int aConst = 0; // Une constante
1.4.14. Références 1/2
-
Les variables de type tableau, énumération, objet ou interface sont en fait des références
-
La valeur d’une telle variable est une référence vers (l’adresse de) une donnée
-
Dans d’autres langages, une référence est appelée pointeur ou adresse mémoire
-
En Java, la différence réside dans le fait qu’on ne manipule pas directement l’adresse mémoire: le nom de la variable est utilisé à la place
-
pas d’arithmétique des pointeurs en Java
-
les références assurent une meilleure sécurité (moins d’erreurs de programmation)
-
-
L’association (l’affectation) d’une donnée à une variable lie l’identificateur et la donnée
1.4.16. Référence vs. pointeur
-
Dans les deux cas, ce sont des variables (ou des constantes) dont la valeur (le contenu) est une adresse mémoire
-
Un pointeur est un concept de bas-niveau permettant une manipulation précise de l’adresse (arithmétique des pointeurs, pointeur de fonction, …)
-
Une référence est une abstraction de plus haut niveau qui fournit une interface plus simple mais plus limitée pour manipuler l’adresse
1.4.17. Gestion de la mémoire dans la JVM
-
Les variables locales (types primitifs et références vers des objets du tas) sont créées sur la pile (stack)
-
Lors de la création d’un object, la mémoire est allouée dans une zone mémoire appelée le tas (heap)
-
La libération de la mémoire est automatique et gérée par le ramasse-miette (garbage collector)
-
le GC s’exécute lorque certaines conditions sont réunies
-
-
Certains paramètres de la JVM permettent de contrôler le GC et les zones mémoires (
-mx|-Xmx,-XX:+UseParallelGC, …)
1.4.18. Tableaux
-
Un tableau est une structure de données regroupant plusieurs valeurs de même type
-
La taille d’un tableau est déterminée lors de sa création (à l’exécution)
-
La taille d’un tableau ne varie pas par la suite
-
Un tableau peut contenir des références
-
tableau d’objets ou tableau de tableaux
-
permet de créer des tableaux à plusieurs dimensions
-
1.4.19. Déclaration et création de tableaux
-
La déclaration d’une variable de type tableau se fait en ajoutant
[]au type des éléments
int[] unTableau;
-
La création du tableau se fait en utilisant l’opérateur
newsuivi du type des éléments du tableau et de sa taille entre[]
new int[10];
-
La référence retournée par
newpeut être liée à une variable
int[] unTableau = new int[10];
-
Il est possible de créer et d’initialiser un tableau en une seule étape
int[] unTableau = { 1, 5, 10 };
1.4.20. Manipulation de tableaux
-
L’accès aux éléments d’un tableau se fait en utilisant le nom du tableau suivi de l’indice entre
[](exemple:unTableau[2]) -
La taille d’un tableau peut être obtenue en utilisant la propriété
length(exemple:unTableau.length) -
La méthode de classe
arraycopydeSystempermet de copier efficacement un tableau
1.4.23. Tableaux et références 3/6
int[] unTableau = new int[10];
int[] leMemeTableau = unTableau;
leMemeTableau[0] = 12;
1.4.24. Tableaux et références 4/6
int[] unTableau = new int[10];
int[] leMemeTableau = unTableau;
leMemeTableau[0] = 12;
int[] unAutreTableau = new int[5];
1.4.25. Tableaux et références 5/6
int[] unTableau = new int[10];
int[] leMemeTableau = unTableau;
leMemeTableau[0] = 12;
int[] unAutreTableau = new int[5];
leMemeTableau = unAutreTableau;
1.4.26. Tableaux et références 6/6
int[] unTableau = new int[10];
int[] leMemeTableau = unTableau;
leMemeTableau[0] = 12;
int[] unAutreTableau = new int[5];
leMemeTableau = unAutreTableau;
leMemeTableau[0] = 21;
1.5. Python RefCard
1.5.1. Le langage Python
-
Créé en 1990 par Guido Van Rossum
-
Caractéristiques
-
compilé/interprété
-
langage de scripts
-
système de typage implicite, dynamique, fort
-
support de la programmation procédurale, OO, fonctionnelle (partiel)
-
| En 2017, deux versions incompatibles de Python co-existent (2.7 et 3.6) |
1.5.2. Quelques domaines d’application
-
Langage de scripts
-
Programmation scientifique
-
analyse de données, bioinformatique (analyse du génôme)
-
-
Développement web
-
plusieurs frameworks populaires
-
-
Développement d’outils pour le développement logiciel
-
Enseignement de l’informatique
1.5.3. Implémentations
-
CPython est l’implémentation de référence
-
Principales implémentations alternatives
-
Jython pour la JVM,
-
IronPython pour .Net,
-
1.5.4. Distributions
1.5.5. Quelques outils de développement
-
REPL : IPython
-
Notebook : Jupyter
-
IDE : Spyder, PyCharm, PyDev, Python pour VS Code, Python Tools for Visual Studio
-
Guide de style : PEP 8, PEP 8 — the Style Guide for Python Code, Google Python Style Guide
-
Documentation : pydoc
-
Audit de code : Pylint
-
Environnement virtuel : virtualenv, virtualenvwrapper, pipenv
-
Tests unitaires : unittest
1.5.6. Lancer un interpréteur interactif
-
Lancer le REPL (sous Linux)
$ python3
Python 3.5.3rc1 (default, Jan 3 2017, 04:40:57)
[GCC 6.3.0 20161229] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> quit()
$
-
Lancer IPython (sous Linux)
$ ipython3
Python 3.5.3rc1 (default, Jan 3 2017, 04:40:57)
Type "copyright", "credits" or "license" for more information.
IPython 5.1.0 -- An enhanced Interactive Python.
? -> Introduction and overview of IPython's features.
%quickref -> Quick reference.
help -> Python's own help system.
object? -> Details about 'object', use 'object??' for extra details.
In [1]: quit
$
1.5.7. Lancer un notebook Jupiter
-
Lancement sur http://localhost:8888/
$ jupyter-notebook
[I 09:55:27.102 NotebookApp] Writing notebook server cookie secret to /run/user/1000/jupyter/notebook_cookie_secret
[W 09:55:27.136 NotebookApp] Widgets are unavailable. On Debian, notebook support for widgets is provided by the package jupyter-nbextension-jupyter-js-widgets
[I 09:55:27.151 NotebookApp] Serving notebooks from local directory: /home/hal
[I 09:55:27.151 NotebookApp] 0 active kernels
[I 09:55:27.151 NotebookApp] The Jupyter Notebook is running at: http://localhost:8888/
[I 09:55:27.151 NotebookApp] Use Control-C to stop this server and shut down all kernels (twice to skip confirmation).
-
Arrêt en tapant deux fois Ctrl+C
1.5.8. Exemple de script Python
intro/first_script.py#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""Ce module est un exemple simple de script Python.
Un commentaire débute par # et se termine en fin de ligne.
Un script débute par un shebang (optionnel).
Le shebang est inutile dans le cas d'un module non exécuté directement
La seconde ligne de commentaire précise l'encodage du fichier (optionnel).
On trouve ensuite la docstring du module qui documente le module lui-même (optionnel).
"""
print(
"""
Ce code est exécuté dans tous les cas.
""")
if __name__ == '__main__':
# L'indentation définit un bloc de code imbriqué
print(
"""
Ce bloc de code est exécuté uniquement quand le module est invoqué
directement par l'interpréteur, i.e. pas importé
""")
1.5.9. Exécuter un programme Python
-
En appelant l’interpréteur
$ python3 intro/first_script.py
Ce code est exécuté dans tous les cas.
Ce bloc de code est exécuté uniquement quand le module est invoqué
directement par l'interpréteur, i.e. pas importé
-
Directement par le shell (utilise le shebang)
$ chmod +x intro/first_script.py
$ intro/first_script.py
1.5.10. Généralités
-
Commentaire introduit par
# -
Différence entre minuscules et majuscules
-
L’indentation définit l’imbrication des blocs
-
Pas de constante
-
variable qu’on ne modifie pas
-
par convention, nommée en majuscule et avec des
_
-
-
Toutes les données sont représentées par des objets
1.5.11. Principaux types prédéfinis 1/2
-
numbers.Numberreprésente les nombres (immuables)-
numbers.Integralpour les entiers-
intnombre entier sans limite de taille -
boolpossède les valeursTrueetFalse
-
-
float(numbers.Real) nombre en virgule flottante double précision -
complex(numbers.Complex) nombre complexe représenté comme une paire defloat
-
-
Nonepossède une seule valeurNone
Les exemples de cette section sont disponibles dans le notebook intro/Python RefCard.ipynb
|
1.5.12. Principaux types prédéfinis 2/2
-
Une séquence représente une collection ordonnée (indexée par des entiers)
-
une séquence immuable ne change pas après sa création
-
une chaîne de caractère est une séquence de caractères unicodes
-
un tuple représente un n-uplet d’objets quelconques
-
-
une séquence mutable supporte les modifications
-
une liste est formée d’éléments quelconques
-
-
-
Un ensemble représente une collection non ordonnée d’éléments uniques
-
un ensemble est modifiable
-
-
Un dictionnaire représente une collection modifiable de couples (clé, valeur)
1.5.13. Littéraux
-
Un littéral est la représentation dans le code source d’une valeur d’un type
-
int:1234,0b11001en binaire,0o177en octal,0xAFFen héxa,_est permis (v 3.6) -
float:3.14,10.,.001,1e100,3.14e-10 -
Imaginaire : littéral
floatsuffixé parjconstruit un complexe de partie réelle nulle (3+4jpour une partie réelle non nulle) -
Chaîne :
"chaîne",'chaîne',"""chaîne multi-lignes""",'''chaîne multi-lignes''', préfixée parrpour éviter l’interprétation, parfpour une chaîne formatée (v 3.6)
1.5.14. Liaison des variables et référence
-
Les variables sont en fait des références
-
La valeur d’une telle variable est une référence vers (l’adresse de) une donnée
-
dans d’autres langages, une référence est appelée pointeur
-
dans les deux cas, ce sont des variables dont la valeur (le contenu) est une adresse mémoire
-
-
Une référence est une abstraction de plus haut niveau
-
fournit une interface plus simple pour manipuler l’adresse
-
ne permet pas de manipuler directement l’adresse mémoire
-
un pointeur est un concept de bas-niveau permettant une manipulation directe de l’adresse (arithmétique des pointeurs, pointeur de fonction, …)
-
-
L’association (l’affectation) d’une donnée à une variable lie l’identificateur et la donnée
1.5.15. Structures de contrôle
-
instruction
pass -
if i < 10:,elif i > 100:,else: -
while i < 10: -
for idx in seq:,for i in range(0, 10, 3): -
break,continue,else:pour une boucle
| Il faut bien respecter l’indentation. |
1.5.16. Quelques opérateurs spécifiques
-
/représente la division en virgule flottante -
//représente la division entière -
**représente l’élévation à la puissance -
a if condition else best une expression conditionnelle -
pas d’opérateur de conversion de types
1.5.17. Fonction
-
Lors de l’appel, les paramètres formels sont liés aux arguments
-
Un appel de fonction retourne toujours une valeur (éventuellement
None) -
Les fonctions peuvent être imbriquées
def fib(n):
"""Print a Fibonacci series up to n."""
a, b = 0, 1
while a < n:
print(a, end=' ')
a, b = b, a+b
print()
fib(2000) # Appel de la fonction
1.5.18. Exemple de manipulation de chaînes
word = 3 * 'un' + 'ium' # 'unununium'
word[0] # character in position 0
word[-1] # last character
word[2:5] # characters from position 2 (included) to 5 (excluded)
word[:2] # character from the beginning to position 2 (excluded)
word[4:] # characters from position 4 (included) to the end
len(word) # size of the string
1.5.19. Exemple de manipulation de tuples
t = () # tuple vide
t = 12345, # singleton
t = 12345, 54321, 'hello!'
t[0] # premier élément
1.5.20. Exemple de manipulation de listes
squares = [1, 4, 9, 16, 25]
squares = [x**2 for x in range(10)] # liste en compréhension
del squares[0] # supprime le premier élément
cubes = [1, 8, 27, 65, 125]
cubes[3] = 64 # modifiable
del cubes # supprime la variable
1.5.21. Exemple de manipulation d’ensembles
-
Un ensemble est une instance de la classe
set
basket = set() # ensemble vide
basket = {'apple', 'orange', 'apple', 'pear', 'orange', 'banana'}
'orange' in basket # test d'appartenance
a = set('abracadabra') # les doublons sont éliminés
b = set('alacazam')
a - b # différence
a | b # union
a & b # intersection
a ^ b # a union b privé de a inter b
a = {x for x in 'abracadabra' if x not in 'abc'} # ensemble en compréhension
1.5.22. Exemple de manipulation de dictionnaires
-
Un dictionnaire est une instance de la classe
dict
tel = {'jack': 4098, 'sape': 4139}
tel['guido'] = 4127
del tel['sape']
1.5.23. Références
-
Une introduction à Python 3, Mémento Python 3, Abrégé Dense Python 3.2, L. Pointal
-
Apprenez à programmer en Python, cours Open Classrooms
-
The Hitchhiker’s Guide to Python! (fr), Kenneth Reitz (livre)
-
Core Python, DZone Refcard
-
Dive Into Python 3, Mark Pilgrim (livre)
-
Invent with Python, Albert Sweigart (livres)
-
Program Arcade Games With Python And Pygame (fr), Paul Craven (livre)
Références (POO/Java)
-
[BK16] D. Barnes and M. Köllin. Objects First with Java. Pearson. 2016.
-
[BG05] J. Bloch and N. Gafter. Java Puzzlers. Addison-Wesley. 2005.
-
[Dar14] I. Darwin. Java Cookbook. O’Reilly Media. 2014.
-
[HC12] C. S. Horstmann and G. Cornell. Core Java - Fundamentals. Prentice Hall. 2015.
-
[HC13] C. S. Horstmann and G. Cornell. Core Java - Advanced Features. Prentice Hall. 2016.
-
[Meyer08] B. Meyer. Conception et programmation orientées objet. Eyrolles. 2008.
-
[UFM14] R.-G. Urma, M. Fusco and A. Mycroft. Java 8 in Action. Manning. 2014.
-
[JavaAPI] The Java API Documentation. 2014.
-
[JavaDevGuide] Java Platform Standard Edition 8 Documentation. 2014.
-
[JavaTutorial] The Java Tutorials. 2014.
1.6. Exercices (Python)
1.6.1. Installation de l’environnement
Vérification de l’installation Python
-
Sous Linux, il est préférable d’utiliser le système de paquets de l’OS
-
python3, ipython3, python3-pip, jupyter-notebook sous Debian,
-
python3, ipython3, python3-pip, jupyter-notebook (non dispo. sous xenial) sous Ubuntu
-
-
Sous Windows, il suffit d’installer une distribution comme Anaconda
-
Sous Mac OS X, utiliser Homebrew. La procédure est détaillée sur la page Installing Python 3 on Mac OS X. La distribution Anaconda est également disponible sous Mac OS X.
-
Vérifiez l’installation en reproduisant les instructions des sections Lancer un interpréteur interactif et Lancer un notebook Jupyter.
-
Création d’un compte Bitbucket
Les plateformes Bitbucket (et Github) permettent d’héberger des projets en utilisant un système de gestion de versions (git).
-
Créez-vous un compte sur Bitbucket.
-
Faites un fork du dépôt https://bitbucket.org/hal_prism/cours.poo.exemples.python.git dans votre espace Bitbucket.
-
Cloner localement votre nouveau dépôt.
Création d’un compte SageMathCloud (optionnel)
La plateforme SageMathCloud offre un ensemble d’outils pour le calcul scientifique. Un compte gratuit donne accès à une machine virtuelle sur laquelle sont installés de nombreux logiciels de calcul scientifique ainsi qu’à une interface graphique dans le navigateur permettant d’ouvrir un notebook, un terminal…
-
Créez-vous un compte sur SageMathCloud.
-
Créez un projet.
-
Ajoutez un terminal dans votre projet pour reproduir les instructions des sections Lancer un interpréteur interactif et Lancer un notebook Jupyter.
1.6.2. Premiers pas en Python
Dans cette section, vous utiliserez comme base le répertoire intro des exemples du cours.
N’hésitez pas à faire des modifications dans les exemples proposés.
-
Exécutez le script de la section Exemple de script Python en suivant les instructions de la section Exécuter un programme Python.
-
Dans un notebook, ouvrez (ou reproduisez) et exécutez le fichier
Python RefCard.ipynbqui reprend les exemples du cours. -
Répondez aux questions suivantes
-
Dans la documentation du langage, où se trouve la documentation de la fonction
print(pour Python 3.6, pour Python 2.7) ? Même question pour l’instructionprintde Python 2.7. -
Où se trouve la documentation de l’instruction
assert? -
Quelle convention utilise-t-on pour nommer les fonctions en Python ?
-
En dehors de la classe
str, quels modules de la bibliothèque standard permettent de manipuler du texte ? -
Dans l’index PyPI, combien de bibliothèques sont liées au thème Games/Entertainment pour la version 3.6 de Python ?
-
En Python 3, quelle est la différence entre les opérateurs
/et//? -
En utilisant les listes en compréhension, générer les tables de multiplication jusqu’à 10.
-
2. Vue d’ensemble des concepts objet
2.1. Objet et message
2.1.1. Système orienté objet
-
Lors de son exécution, un système OO est un ensemble d’objets qui interagissent
-
Les objets forment donc l'aspect dynamique (à l’exécution) d’un système OO
-
Ces objets représentent soit
-
des entités du monde réel (⇒ ce sont donc des modèles d’entités réelles), soit
-
des objets "techniques" nécessaires durant l’exécution du programme.
-
2.1.2. Objet
-
Un objet est formé de deux composants indissociables
-
son état, i.e. les valeurs prises par des variables le décrivant (propriétés)
-
son comportement, i.e. les opérations qui lui sont applicables
-
-
Un objet est une instance d’une classe
-
Un objet peut avoir plusieurs types, i.e. supporter plusieurs interfaces
2.1.3. Exemple : des points et des cercles
-
Les objets
point1etpoint2sont des points,cercle1est un cercle -
L’état de chaque objet est représenté par la valeur de ses propriétés
-
Le centre du cercle est une référence sur un objet point
-
Le comportement n’est pas représenté au niveau des objets
-
une opération est invoquée par rapport à un objet
-
mais est rattachée à la classe (le code est partagé par tous les objets d’une classe)
-
-
Les objets
point1etpoint2sont égaux mais pas identiques
2.1.4. Exemple : des points et des cercles (Java)
Point2D p1 = new Point2D(1.0, 2.0);
Point2D p2 = new Point2D(1.0);
Point2D p3 = new Point2D();
Point2D unAutreP3 = p3;
assert p3 == unAutreP3; // 2 points identiques
Cercle2D c1 = new Cercle2D(p1, 3.0);
Cercle2D c2 = new Cercle2D(new Point2D(2.0, 4.0), 5.0);
Cercle2D c3 = new Cercle2D();
2.1.5. Exemple : des points et des cercles (Python)
point1 = point2d.Point2D(1.0, 2.0)
point2 = point2d.Point2D(1.0, 2.0)
cercle1 = cercle2d.Cercle2D(point1, 2.0)
2.1.6. Communication par messages
-
Un objet solitaire n’a que peu d’intérêt ⇒ différents objets doivent pouvoir interagir
-
Un message est un moyen de communication (d’interaction) entre objets
-
Les messages sont les seuls moyens d’interaction entre objets
-
⇒ l’état interne ne doit pas être manipulé directement
-
-
Le (ou les) type(s) d’un objet détermine les messages qu’il peut recevoir
2.1.7. Message
-
Un message est une requête envoyée à un objet pour demander l’exécution d’une opération
-
Un message comporte trois composants:
-
l’objet auquel il est envoyé (le destinataire du message),
-
le nom de l’opération à invoquer,
-
les paramètres effectifs.
-
2.1.8. Exemple : déplacer un cercle
-
L’utilisateur envoie un message à un objet (à une instance de) cercle
-
Le message se traduit par l’exécution de l’opération
translate(1.0, 2.0)par l’objet cercle -
Durant cette exécution, le cercle envoie un message à l’objet point (translater le cercle revient à translater son centre)
2.1.9. Exemple : déplacer un cercle (Java)
-
Le message translate est envoyé à
cercle1(cercle1.translate(1.0, 2.0))-
lors de cette exécution, le message translate est envoyé au centre du cercle (
centre.translate(1.0, 2.0))-
l’opération
translatedu point est exécutée
-
-
2.1.10. Exemple : déplacer un cercle (Python)
cercle1.translate(2.0, 3.0)
-
Le message translate est envoyé à
cercle1(cercle1.translate(1.0, 2.0))-
lors de cette exécution , le message translate est envoyé au centre du cercle (
self.centre.translate(1.0, 2.0))-
l’opération
translatedu point est exécutée
-
-
2.2. Type et classe
2.2.1. Type
-
Un type est un modèle abstrait réunissant à un haut degré les traits essentiels de tous les êtres ou de tous les objets de même nature
-
En informatique, un type (de donnée) spécifie:
-
l’ensemble des valeurs possibles pour cette donnée (définition en extension),
-
l’ensemble des opérations applicables à cette donnée (définition en intention).
-
-
Un type spécifie l'interface par laquelle une donnée peut être manipulée
2.2.3. Exemple : un type Déplaçable (Java)
-
En Java, un type peut être représenté par une interface.
package fr.uvsq.info.poo.coo;
public interface Deplacable {
/**
* Translate l'objet.
*
* @param dx deplacement en abscisse
* @param dy deplacement en ordonnées
*/
void translate(double dx, double dy);
}
2.2.4. Type (Python)
-
En Python, tout objet acceptant les messages déclarés dans un type est de ce type
-
Cette propriété se nomme duck typing
-
Elle est commune à plusieurs langages à typage dynamique
2.2.5. Classe I
-
Une classe est un "modèle" (un "moule") pour une catégorie d’objets structurellement identiques
-
Une classe définit donc l’implémentation d’un objet (son état interne et le codage de ses opérations)
-
L’ensemble des classes décrit l'aspect statique d’un système OO
2.2.6. Classe II
-
Une classe comporte:
-
la définition des attributs (ou variables d’instance),
-
la signature des opérations (ou méthodes),
-
la réalisation (ou définition) des méthodes.
-
-
Chaque instance aura sa propre copie des attributs (son état)
-
La signature d’une opération englobe son nom et le type de ses paramètres
-
L’ensemble des signatures de méthodes représente l’interface de la classe (publique)
-
L’ensemble des définitions d’attributs et de méthodes forme l’implémentation de la classe (privé)
2.2.7. Exemple : les classes Cercle2D et Point2D
-
Un rectangle représente une classe
-
1er pavé: nom de la classe
-
2ième pavé: attributs
-
3ième pavé: signature des méthodes
-
-
en général, les attributs sont privés et les méthodes publiques
2.2.9. Exemple : la classe Cercle2D (Java)
package fr.uvsq.info.poo.coo;
public class Cercle2D implements Deplacable {
/** Le centre du cercle. */
private Point2D centre;
/** Le rayon du cercle */
private double rayon;
/**
* Initialise un cercle avec un centre et un rayon.
* @param centre Le centre
* @param rayon Le rayon
*/
public Cercle2D(Point2D centre, double rayon) { /* ... */ }
/**
* Initialise un cercle centré à l'origine et de rayon 1.
*/
public Cercle2D() { /* ... */ }
public Point2D getCentre() { return centre; }
public double getRayon() { return rayon; }
/**
* Translate le cercle.
* @param dx déplacement en abscisse.
* @param dy déplacement en ordonnées.
*/
public void translate(double dx, double dy) { /* ... */ }
}
2.2.10. Exemple : la classe Cercle2D (Python)
"""Définition de la classe Cercle2D"""
from coo.forme import Forme
class Cercle2D(Forme):
"""Représente un cercle en 2 dimensions"""
def __init__(self, centre, rayon):
"""Initialise un cercle à partir d'un point et d'un rayon"""
self.centre = centre
self.rayon = rayon
def __str__(self):
return "Cercle2D({}, {})".format(self.centre, self.rayon)
def translate(self, dx, dy):
"""Déplace le cercle"""
self.centre.translate(dx, dy)
2.2.11. Classe et type
-
Une classe implémente un ou plusieurs types, i.e. respecte une ou plusieurs interfaces
-
Un objet peut avoir plusieurs types mais est une instance d’une seule classe
-
Des objets de classes différentes peuvent avoir le même type
2.2.13. Instanciation d’une classe
-
Le mécanisme d'instanciation permet de créer des objets à partir d’une classe
-
Chaque objet est une instance d’une classe
-
Lors de l’instanciation,
-
de la mémoire est allouée pour l’objet,
-
l’objet est initialisé (appel du constructeur) afin de respecter l’invariant de la classe.
-
2.2.14. Application : modélisation d’un robot
On veut modéliser des robots se déplaçant sur un terrain. Ce terrain est découpé en cases carrées repérées par deux coordonnées. Chaque case peut être vide ou contenir un mur ou un robot. Les robots sont très rudimentaires et ne disposent que d’une boussole. Ils ne connaissent donc que leur orientation (Nord, Est, Sud, Ouest). Un robot doit pouvoir avancer d’une case et tourner d’un quart de tour à droite. Un robot ne peut se déplacer d’une case à une autre que si la case de destination est vide.
-
Représenter en UML la classe
Robot -
Faire de même avec la classe
Terrain -
Représenter sur un diagramme de séquence les échanges de messages pour le déplacement d’un robot
On suppose maintenant qu’un robot peut en détecter un autre qui passe devant lui. Par exemple, quand un robot se déplace, il peut passer dans le champ de vision d’un autre. Ce dernier devra alors être averti.
-
Modifier le diagramme précédent pour y intégrer la détection des déplacements
2.3. Héritage
2.3.1. Sous-type
|
Un type T1 est un sous-type d’un type T2 si l’interface de T1 contient l’interface de T2. De façon duale, un type T1 est un sous-type d’un type T2 si l’ensemble des instances de T2 inclut l’ensemble des instances de T1. |
-
Un sous-type possède une interface plus riche, i.e. au moins toutes les opérations du super-type
-
De manière équivalente, l’extension du super-type contient l’extension du sous-type, i.e. tout objet du sous-type est aussi instance du super-type
2.3.3. Héritage
-
L'héritage permet de définir l’implémentation d’une classe à partir de l’implémentation d’une autre
-
Ce mécanisme permet, lors de la définition d’une nouvelle classe, de ne préciser que ce qui change par rapport à une classe existante
-
Une hiérarchie de classes permet de gérer la complexité, en ordonnant les classes au sein d’arborescences d’abstraction croissante
-
Si Y hérite de X, on dit que
-
Y est une classe fille (sous-classe, classe dérivée) et que
-
X est une classe mère (super-classe, classe de base)
-
2.3.5. Exemple : la classe Rectangle2DPlein (Java)
/**
* Un rectangle plein en deux dimensions.
*
* @author Stéphane Lopes
* @version nov. 2008
*/
class Rectangle2DPlein extends Rectangle2D {
/**
* La couleur de remplissage
*/
private Color couleur;
/**
* Initialise le rectangle plein.
*
* @param supGauche Le coin supérieur gauche.
* @param infDroit Le coin inférieur droit.
* @param couleur La couleur de remplissage.
*/
public Rectangle2DPlein(Point2D supGauche,
Point2D infDroit,
Color couleur) {
super(supGauche, infDroit);
assert couleur != null;
this.couleur = couleur;
}
/**
* Renvoie la couleur.
*
* @return la couleur.
*/
public Color getCouleur() {
return couleur;
}
2.3.6. Exemple : utilisation de la classe Rectangle2DPlein (Java)
// Déclaration et création d'un rectangle rouge
Rectangle2DPlein rp = new Rectangle2DPlein(new Point2D(1.0, 2.0),
new Point2D(3.0, 0.0),
Color.RED);
assert rp.getCouleur() == Color.RED;
// Déclaration d'un rectangle et
// liaison avec un rectangle plein
Rectangle2D r = new Rectangle2DPlein(new Point2D(1.0, 2.0),
new Point2D(2.0, 1.0),
Color.YELLOW);
assert r.getLargeur() == 1;
-
L’affectation d’une instance de
Rectangle2DPleinà une références sur unRectangle2Dest autorisée -
À partir de
r1, l’accès àgetCouleurest impossible (échoue à la compilation) cargetCouleurne fait pas parti de l’interface deRectangle2D
2.3.7. Exemple : la classe Rectangle2DPlein (Python)
"""Définition de la classe Rectangle2DPlein"""
from coo.rectangle2d import Rectangle2D
class Rectangle2DPlein(Rectangle2D):
def __init__(self, orig, tailleX, tailleY, couleur):
"""Initialise un rectangle à partir d'un point, d'une taille et d'une couleur"""
Rectangle2D.__init__(self, orig, tailleX, tailleY)
self.couleur = couleur
def __str__(self):
return "Rectangle2DPlein({}, {}, {})".format(self.orig, self.fin, self.couleur)
def colorie(self, couleur):
"""Modifie la couleur du rectangle"""
self.couleur = couleur
-
La super-classe est indiqué entre parenthèse après le nom de la classe
-
Le constructeur de la super-classe est appelé dans le constructeur
2.3.8. Héritage et sous-typage
-
L’héritage (ou héritage d’implémentation) est un mécanisme technique de réutilisation
-
Le sous-typage (ou héritage d’interface) décrit comment un objet peut être utilisé à la place d’un autre
-
Si Y est une sous-type de X, cela signifie que "Y est une sorte de X" (relation IS-A)
-
Dans un langage de programmation, les deux visions peuvent être représentées de la même façon: le mécanisme d’héritage permet d’implémenter l’un ou l’autre
2.3.10. Polymorphisme
-
Le polymorphisme est l’aptitude qu’ont des objets à réagir différemment à un même message
-
L’intérêt est de pouvoir gérer une collection d’objets de façon homogène tout en conservant le comportement propre à chaque type d’objet
-
Une méthode commune à une hiérarchie de classe peut avoir plusieurs implémentations dans différentes classes
-
Une sous-classe peut redéfinir une méthode de sa super-classe pour spécialiser son comportement
-
Le choix de la méthode à appeler est retardé jusqu’à l’exécution du programme (liaison dynamique ou retardée)
2.3.12. Exemple : la description dans Rectangle2D (Java)
/**
* Retourne une chaîne représentant l'objet.
* @return la chaîne.
*/
@Override
public String toString() {
return String.format("O = %s L = %s, H = %s", orig, getLargeur(), getHauteur());
}
2.3.13. Exemple : la description dans Rectangle2DPlein (Java)
/**
* Retourn une chaîne représentant l'objet.
*
* @return la chaîne.
*/
@Override
public String toString() {
return String.format("%s, couleur : %s", super.getDesc(), couleur);
}
2.3.14. Exemple : la description dans Rectangle2D (Python)
"""Définition de la classe Rectangle2D"""
from coo.forme import Forme
from coo.point2d import Point2D
class Rectangle2D(Forme):
def __init__(self, orig, tailleX, tailleY):
"""Initialise un rectangle à partir d'un point et d'une taille en X et Y"""
self.orig = orig
self.fin = Point2D(orig.x + tailleX, orig.y + tailleY)
def __str__(self):
return "Rectangle2D({}, {})".format(self.orig, self.fin)
def translate(self, dx, dy):
"""Déplace le rectangle de dx en abscisse et de dy en ordonnée"""
self.orig.translate(dx, dy)
self.fin.translate(dx, dy)
-
La méthode
strfournit la description
2.3.15. Exemple : la description dans Rectangle2DPlein (Python)
"""Définition de la classe Rectangle2DPlein"""
from coo.rectangle2d import Rectangle2D
class Rectangle2DPlein(Rectangle2D):
def __init__(self, orig, tailleX, tailleY, couleur):
"""Initialise un rectangle à partir d'un point, d'une taille et d'une couleur"""
Rectangle2D.__init__(self, orig, tailleX, tailleY)
self.couleur = couleur
def __str__(self):
return "Rectangle2DPlein({}, {}, {})".format(self.orig, self.fin, self.couleur)
def colorie(self, couleur):
"""Modifie la couleur du rectangle"""
self.couleur = couleur
-
La méthode
strest redéfinie dansRectangle2DPlein
2.3.16. Classe abstraite
-
Une classe abstraite représente un concept abstrait qui ne peux pas être instancié
-
En général, son comportement ne peut pas être intégralement implémenté à cause de son niveau de généralisation
-
Elle sera donc seulement utilisée comme classe de base dans une hiérarchie d’héritage
2.3.18. Exemple : une classe abstraite (Java)
/**
* Une figure fermée.
*
* @version jan. 2017
* @author Stéphane Lopes
*
*/
abstract class FigureFermee2D {
/**
* Translate la figure.
* @param dx déplacement en abscisse.
* @param dy déplacement en ordonnée.
*/
public abstract void translate(double dx, double dy);
}
2.3.19. Exemple : redéfinition d’une méthode (Java)
Rectangle2D/**
* Translate le rectangle.
* @param dx déplacement en abscisse.
* @param dy déplacement en ordonnées.
*/
@Override
public void translate(double dx, double dy) {
orig.add(dx, dy);
fin.add(dx, dy);
}
Cercle2D/**
* Translate le cercle.
*
* @param dx déplacement en abscisse.
* @param dy déplacement en ordonnées.
*/
@Override
public void translate(double dx, double dy) {
centre.add(dx, dy);
}
2.3.20. Exemple : utilisation d’une classe abstraite (Java)
// Création du tableau de références
final int NB_FIGURES = 4;
FigureFermee2D[] figures = new FigureFermee2D[NB_FIGURES];
// Création des formes
figures[0] = new Rectangle2D(new Point2D(0.0, 5.0),
new Point2D(2.0, 2.0));
figures[1] = new Cercle2D(new Point2D(1.0, 2.0), 3.0);
figures[2] = new Rectangle2D(new Point2D(5.0, 5.0),
new Point2D(7.0, 3.0));
figures[3] = new Cercle2D(new Point2D(4.0, 5.0), 2.0);
// Réalise une translation de la figure
for (int i = 0; i < figures.length; ++i) {
figures[i].translate(1.0, 2.0);
}
2.3.21. Exemple : la classe abstraite Forme (Python)
"""Classe de base pour les formes"""
from abc import ABC, abstractmethod
class Forme(ABC):
@abstractmethod
def translate(self, dx, dy):
"""Déplace la forme d'un décalage en x et en y"""
pass
2.3.22. Héritage multiple et à répétition
-
Un héritage multiple se produit lorsqu’une classe possède plusieurs super-classes
-
Un héritage à répétition se produit lorsqu’une classe hérite plusieurs fois d’une même super-classe
-
Ces types d’héritage peuvent provoquer des conflits aux niveaux des attributs et méthodes
-
deux classes de base peuvent posséder la même méthode,
-
un attribut peut être hérité selon plusieurs chemins dans le graphe d’héritage.
-
2.3.23. Exemple : une hiérarchie pour les véhicules
-
Combien de numéros d’immatriculation possède la voiture amphibie ?
-
Quelle opération est invoquée quand une voiture amphibie reçoit le message
avance?
2.3.24. Exemple : la hiérarchie de véhicules (Python)
#!/usr/bin/env python3
"""Exemple d'héritage multiple"""
class Vehicule(object):
def __init__(self, immat):
self.immat = immat
def avance(self, distance):
print("avance dans Vehicule")
class Bateau(Vehicule):
def __init__(self, immat):
Vehicule.__init__(self, immat)
def avance(self, distance):
print("avance dans Bateau")
class Voiture(Vehicule):
def __init__(self, immat):
Vehicule.__init__(self, immat)
def avance(self, distance):
print("avance dans Voiture")
class VoitureAmphibie(Bateau, Vehicule):
pass
if __name__ == '__main__':
va = VoitureAmphibie("VA123")
va.avance(12)
print(va.immat)
2.4. Relations entre classes
2.4.1. Types de relations entre classes
- Dépendance
-
-
liaison limitée dans le temps entre objets (non structurelle)
-
- Association
-
-
relation structurelle
-
cas particuliers : Agrégation, Composition
-
- Spécialisation/généralisation
-
-
relation d’héritage ou de sous-typage
-
cas particulier : Réalisation (relation entre une classe et un type)
-
2.4.2. Relation de dépendance
-
Un élément A dépend d’un élément B si A utilise les services de B, i.e. son interface
-
Un changement dans l’interface de B peut donc avoir des répercussions sur A
-
par contre, son implémentation peut être modifiée sans affecter A
-
-
Une objet local à une méthode ou un paramètre de méthode sont des exemples de ce type de relation
2.4.3. Association
-
Une association représente une connexion sémantique bidirectionnelle entre une (association réflexive) ou plusieurs classes
-
les participants de l’association possèdent des références des uns vers les autres
-
est donc structurelle et permanente
-
-
L'arité d’une association représente le nombre de participants à l’association (association binaire, ternaire, n-aire)
2.4.5. Exemple : "Apparaît Dans" (Java)
class ApparaitDans {
private Cercle figure;
private Dessin dessin;
private int nbOccurences;
public ApparaitDans(Cercle figure, Dessin dessin, int nbOccurences) {
this.figure = figure;
this.dessin = dessin
this.nbOccurences = nbOccurences;
}
// ...
}
2.4.6. Exemple : "Apparaît Dans" (Python)
class Cercle:
pass
class Dessin:
pass
class ApparaitDans:
def __init__(self, dessin, cercle, position):
self.dessin = dessin
self.cercle = cercle
self.position = position
2.4.8. Exemple : une association ternaire (Java)
class Cours {
private Etudiant etudiant;
private Professeur professeur;
private Salle salle;
private int jour;
private int heure;
private int duree;
// ...
}
2.4.9. Exemple : une association ternaire (Python)
class Cours:
def __init__(self, etudiant, professeur, salle,
jour, heure, durée):
self.etudiant = etudiant
self.professeur = professeur
self.salle = salle
self.jour = jour
self.heure = heure
self.duree = duree
2.4.10. Agrégation
-
L'agrégation est une association non symétrique, qui exprime un couplage fort et une relation de subordination
-
représente une relation de type "ensemble/élément" (ou tout/partie) entre des classes
-
-
La classe représentant l’ensemble est parfois appelée agrégat
-
À un même moment, une instance d’élément agrégé peut être liée à plusieurs agrégats (l’élément agrégé peut être partagé)
-
une instance d’élément agrégé peut exister sans agrégat (et inversement)
-
les cycles de vies de l’agrégat et de ses éléments agrégés peuvent être indépendants
-
si on détruit une agrégation, on ne détruit pas automatiquement ses composants
-
agrégation par "référence"
-
-
L’agrégat est en général responsable de la création/suppression du composant
-
⇒ doit savoir comment créer le composant
-
2.4.12. Exemple : Figure et Cercle (Java)
class Figure {
/** L'ensemble de cercles composants la figure. */
private List<Cercle> elements;
// ...
}
2.4.13. Exemple : Figure et Cercle (Python)
class Figure:
def __init__(self, listeDeCercles):
self.listeDeCercles = copy.copy(listeDeCercles)
2.4.14. Composition
-
Une composition est une agrégation forte (agrégation par valeur)
-
À un même moment, une instance de composant ne peut être liée qu’à un seul agrégat
-
les cycles de vies des éléments (les "composants") et de l’agrégat sont liés
-
si l’agrégat est détruit (ou copié), ses composants le sont aussi
-
| Le choix de modélisation entre agrégation et composition est souvent subjectif |
2.4.16. Exemple : le centre d’un Cercle (Java)
class Cercle {
/** Le centre du cercle. */
private Point centre;
public Cercle(final Point centre, final double rayon) {
this.centre = centre.clone();
// ...
}
// ...
}
2.4.17. Exemple : le centre d’un Cercle (Python)
class Cercle:
def __init__(self, centre, rayon):
self.centre = copy.deepcopy(centre)
self.rayon = rayon
2.4.18. Spécialisation/Généralisation
-
Une classe fille hérite de l’ensemble des caractéristiques des super-classes
-
tout changement dans la classe mère peut entraîner des changements dans la classe fille
-
-
Hériter d’un type limite l’impact des changements
-
seule l’interface est réutilisée
-
-
Contrôler la visibilité des membres d’une classe permet également de limiter l’impact des changements
2.4.19. Relations et dépendances
-
Les relations entre classes peuvent être ordonnées par rapport aux dépendances qu’elles génèrent
Source : Dependency (blog)
2.4.20. Dépendances et changements
-
Quand une dépendance existe entre deux éléments, un changement de l’un affecte le second
-
Le couplage est une mesure de la probabilité qu’un changement d’un élément entraîne une modification de l’autre
-
En développement logiciel, les dépendances entre élément doivent être minimisées
-
pour construire des composantes faiblement couplés
-
le changement d’un composant aura moins d’impact sur le reste du code
-
2.5. Module
2.5.1. Module
-
Un module (ou package) est l’unité de base de décomposition d’un système
-
Il permet d’organiser logiquement des modèles
-
Un module s’appuie sur la notion d'encapsulation
-
publie une interface, i.e. ce qui est accessible de l’extérieur
-
utilise le principe de masquage de l’information, i.e. ce qui ne fait pas parti de l’interface est dissimulé
-
2.5.2. Utilité d’un module
-
Sert de brique de base pour la construction d’une architecture
-
Représente le bon niveau de granularité pour la réutilisation
-
Est un espace de noms qui permet de gérer les conflits
2.5.3. Qualité d’un module
-
La conception d’un module devrait conduire à un couplage faible et une forte cohésion
- couplage
-
désigne l’importance des liaisons entre les éléments ⇒ doit être réduit
- cohésion
-
mesure le recouvrement entre un élément de conception et la tâche logique à accomplir ⇒ doit être élevé, i.e. chaque élément est responsable d’une tâche précise
2.5.4. Exemple : une architecture multi-couches
2.5.5. Package (Java)
-
Le concept de module est implémenté par les packages
-
Le mot clé
packageplacé en début de fichier permet l’ajout d’éléments dans un module -
Le mot clé
importpermet l’accès aux éléments d’un module
2.5.6. Module (Python)
-
Un module est un fichier source contenant des définitions et des instructions Python
-
le fichier source doit être nommé comme le module et avec l’extension
.py
-
-
À l’intérieur d’un module, son nom est accessible par la variable globale
name-
un module exécuté comme un script possède le nom
main
-
-
Un module peut ensuite être importé dans un autre
-
Les modules sont recherchés par l’interpréteur dans les répertoires de la variable
sys.path
2.5.7. Package (Python)
-
Un package regroupe et structure un ensemble de modules
-
Chaque répertoire d’un package doit contenir un fichier
init.py(même vide) -
Un module d’un package est référencé en séparant par un
.les noms des éléments-
par exemple,
sound.effects.echofait référence au moduleechodu sous-packageeffectsdu packageecho
-
2.6. Exercices (Python)
Dans cette section, la bibliothèque pygame est utilisée pour l’affichage graphique (documentation de l’API).
2.6.1. Une application graphique simple
-
Créez un script
simple_app.pydans le répertoiregui. -
Importez la classe
PygameApplicationdu modulepgutil.pygame_application. -
Créez une instance de la classe
PygameApplication -
Explorez le contenu de la classe
-
quelles sont les grandes étapes de l’éxécution ?
-
quel attribut encapsule la logique de l’affichage ?
-
-
Définissez une sous-classe de
PygameScene.-
quelles méthodes propose cette class ?
-
quand sont-elles invoquées ?
-
redéfinissez la méthode
drawpour afficher un cercle au centre de la fenêtre.
-
2.6.2. Gérer la souris
-
Copiez le script précédent sous le nom
simple_app_with_mouse.py. -
Affichez maintenant un nouveau cercle à l’endroit où l’utilisateur clique.
-
Même question mais le cercle doit cette fois être déplacé, i.e. un seul cercle est présent à l’écran.
2.6.3. Échapper aux robots
Le but de cet exercice est de développer un jeu simple où un joueur, Tux, doit échapper à des robots.
Ce jeu fait appel à la notion de Sprite (tutoriel).
-
Selon le même modèle que précédement, créez une application
robot_escape.pyutilisant une scènerobot_escape_scene.py. -
Définissez la classe
RobotSpritecomme sous-classe de la classeSpritede pygame.-
dans le constructeur, charger l’image du robot et initialiser le sprite
-
ajouter la méthode
updatequi permet le déplacement du robot -
intégrez le robot dans la scène
-
vous pouvez définir plusieurs robots avec des déplacements différents.
-
-
Définissez la classe
PlayerSpritequi représente le joueur humain. Il doit pouvoir être déplacé à l’aide des touches fléchées. -
Ajoutez la détection de collision entre le joueur et le (ou les) robots. Par exemple, suite à une collision, vous pouvez faire redémarrer le jeu dans la situation initiale.
-
Ajoutez un objectif (une zone identifiée sur le plateau) : le joueur gagne s’il atteint cet objectif
2.7. Exercices (Java)
2.7.1. Découverte de BlueJ
Dans cette exercice, vous apprendrez les manipulations de base de l’environnement BlueJ.
-
Ouvrez le projet d’exemple
people2(Projects/Open Projet…). Les projets d’exemples se trouvent dans le répertoire d’installation de BlueJ sous Windows ou dans/usr/share/doc/BlueJsous Linux.-
Quelles classes y-a-t-il dans ce projet ?
-
Quelles relations existe-t-il entre ces classes ?
-
-
Compilez le projet (Bouton Compile)
-
Quels logiciels faut-il pour compiler un projet en Java ? pour l’exécuter ?
-
-
Création d’objet (menu contextuel d’une classe)
-
Quels constructeurs sont disponibles pour chaque classe ?
-
Pourquoi la classe
Personn’en a-t-elle aucun ? -
Créez un employé (Staff) p1 ayant pour nom Smith, pour année de naissance 1980 et pour numéro de bureau D100.
-
-
Invocation de méthode (menu contextuel d’un objet)
-
Quelles méthodes propose p1 ?
-
Exécutez les méthodes
getRoom()etgetName. Dans quelles classes sont-elles définies ? -
Exécutez la méthode
setAddress.
-
-
Évaluation à la volée de code Java (View/Show Code Pad). Les manipulations ci-dessous sont à réaliser dans le Code Pad.
-
Créez un étudiant en précisant son nom, son année de naissance et son identifiant.
-
Copiez-le (glisser-déposer) dans la fenêtre des objets.
-
Exécutez la méthode
getNamedans le Code Pad.
-
-
Inspection d’un objet (menu contextuel d’un objet)
-
Inspectez l’objet p1 ?
-
De quels attributs son état est-il formé ?
-
Pouvez-vous inspecter tous ses attributs ? Pourquoi ?
-
-
Éditez le code Java de la classe
Staff(double-click sur la classe)-
Quels éléments du langage trouve-t-on dans cette classe ?
-
À quoi correspondent les couleurs de fond ?
-
Visualisez la documentation de la classe (liste déroulante en haut à droite).
-
Quelle relation y-a-t-il entre la documentation et le code source ? Modifiez un commentaire dans le code et visualisez à nouveau la documentation ?
-
Quel parallèle peut-on établir avec les fichiers
.het.cdu langage C ?
-
2.7.2. Manipulation d’objets
Dans cette exercice, vous effectuerez les manipulations tout d’abord de façon interactive, puis en tapant les instructions dans le Code Pad.
-
Ouvrez le projet d’exemple BlueJ shapes et compiler-le.
-
Réalisez les manipulations suivantes
-
Créez un cercle et rendez-le visible.
-
Déplacez-le horizontalement de 100 unités.
-
Changez sa couleur en rouge.
-
Déplacez-le verticalement de 50 unités.
-
-
Représentez la scène suivante :
-
une maison (carré bleu et triangle rouge),
-
un soleil jaune au-dessus et à droite de la maison.
-
2.7.3. Modification d’une classe
Cet exercice utilise le projet Entreprise (cf. figure ci-dessous) à ouvrir sous BlueJ (Project/Open Non Bluej…).
Entreprise-
Modifiez la classe
Employecomme suit:-
ajoutez l’attribut privé
age, -
modifiez les méthodes pour prendre en compte ce changement,
-
recompilez et testez ce changement.
-
-
Mettez à jour les commentaires Javadoc et vérifier la documentation de la classe.
-
Ajoutez la méthode
toStringà la classeEntreprise: cette méthode retourne une chaîne de caractères contenant le nom de l’entreprise et la liste de ses employés (prénom, nom et âge), -
recompiler les deux classes et tester les nouvelles méthodes.
2.7.4. Utilisation d’un débogueur
Cet exercice utilise le même projet que l’exercice précédent.
Pour chaque test, vous créerez une entreprise et deux employés que l’entreprise embauchera puis vous invoquerez toString sur l’entreprise.
-
Faites afficher le débogueur intégré à BlueJ (View/Show Debugger). Quels éléments sont visibles dans le débogueur ? Quelles actions peut-on effectuer à partir du débogueur ?
-
Lancer un premier test et vérifier le comportement du programme.
-
Lancer un second test en saisissant la valeur
nulllors de l’embauche du deuxième employé-
Que se passe-t-il ?
-
Quelle instruction pose problème ?
-
-
Placer un point d’arrêt (breakpoint) au début de la méthode
toStringdeEntreprise(click dans la marge de l’éditeur). -
Reproduisez le second test:
-
l’exécution doit s’arrêter au niveau du breakpoint,
-
faites exécuter la méthode pas à pas en consultant la valeur des variables,
-
quelle différence existe-t-il entre les commandes Step et Step Into du débogueur ?
-
pour quel employé le problème se pose-t-il ?
-
quelle est donc finalement la cause de l’erreur ?
-
-
Modifiez la classe
Entreprisepour éviter le problème. -
Vérifiez en reproduisant à nouveau le second test.
3. Objets et classes
3.1. Caractéristiques d’un objet
3.1.1. État d’un objet
-
L’état d’un objet est l’ensemble de ses caractéristiques internes, cachées aux autres objets
-
Les propriétés représentant l’état d’un objet particulier sont appelées variables d’instance ou attributs ou données membres
-
Chaque objet possède un état courant décrit par la valeur de ses attributs et donc sa propre copie des variables d’instance
-
Les attributs conservent leurs valeurs durant toute la durée de vie d’un objet
-
Les attributs peuvent être des objets
-
un objet peut donc être la racine d’une arborescence d’autres objets
-
3.1.2. Comportement d’un objet
-
Le comportement d’un objet regroupe les caractéristiques externes mises à la disposition des autres objets
-
Chaque opération est décrite par sa signature (son nom, les objets qu’elle prend comme paramètre et le type de retour)
-
L’ensemble de ces opérations forme l'interface de l’objet
-
Les opérations propres à un objet sont appelées méthodes d’instance ou fonctions membres (fonctions associées à l’objet)
-
Ces opérations sont invoquées par rapport à un objet particulier
-
une méthode sait quel objet l’a invoquée
-
donc, une méthode a accès aux attributs de l’objet qui l’a invoquée
-
comme une fonction avec un paramètre caché vers l’objet lui-même
-
-
Les opérations sont les seules moyens de manipuler l’état interne d’un objet (encapsulation)
3.1.3. Création d’objets
-
Un objet est créé (instancié) à partir d’une classe
-
certains (rares) langages créent de nouveaux objets par clonage d’un objet (prototype)
-
-
La création provoque la réservation de mémoire pour l’objet et l’invocation du constructeur qui initialise l’objet
-
le constructeur est une méthode invoquée automatiquement lors de la création de l’objet
-
son rôle est d’initialiser l’objet pour que ce dernier respecte l’invariant de la classe
-
-
L’opération de création retourne une référence sur l’objet
-
Cette référence doit être liée (affectée) à un identificateur (variable) pour permettre l’accès à l’objet
3.1.4. Identité et égalité
-
Un objet possède une identité (identifiant interne) qui caractérise son existence de façon indépendante de son état
-
tester l’identité de deux objets compare leurs identités
-
-
Deux objets sont égaux si leurs états sont les mêmes
-
tester l’égalité de deux objets compare leurs attributs
-
|
égalité ≠ identité
|
3.1.6. Affectation et copie d’objets
-
L'affectation d’un objet à une variable crée un lien entre la variable et l’objet
-
Créer une copie d’un objet nécessite de créer récursivement une copie de ses attributs (copie profonde ou deep copy)
-
Une autre sémantique possible est de ne copier que l’objet racine et de partager ses attributs (copie superficielle ou shallow copy)
|
3.2. Les objets en Java
3.2.1. Création d’objets
-
Un objet est créé à partir d’une classe en utilisant le mot-clé
new(new Point2D(1.0, 2.0)) -
L’utilisation de
newprovoque la réservation de mémoire pour l’objet et l’invocation du constructeur qui initialise l’objet -
newretourne une référence sur l’objet créé -
Cette référence doit être liée à une variable pour permettre l’accès à l’objet
3.2.2. Déclaration et affectation
-
La syntaxe pour la déclaration d’une variable est
type nom(Point2D p1)-
la déclaration ne crée pas d’objet mais uniquement une référence
-
la variable est invalide tant qu’elle n’est pas liée à un objet (null reference)
-
-
Une affectation va lier une variable à un objet
variable = objet -
Il est possible de lier la variable lors de sa déclaration (
Point2D p1 = new Point2D(1.0, 2.0);) -
Il est conseillé de toujours initialiser une variable lors de sa déclaration (si possible)
3.2.3. Exemple : instanciation de cercle et de points
Point2D p1 = new Point2D(1.0, 2.0);
Point2D p2 = new Point2D(1.0);
Point2D p3 = new Point2D();
Point2D unAutreP3 = p3;
assert p3 == unAutreP3; // 2 points identiques
Cercle2D c1 = new Cercle2D(p1, 3.0);
Cercle2D c2 = new Cercle2D(new Point2D(2.0, 4.0), 5.0);
Cercle2D c3 = new Cercle2D();
3.2.4. Manipulation
Accès aux attributs
-
Juste par le nom de l’attribut quand on se trouve dans la portée de l’attribut (exemple:
x) -
En qualifiant/préfixant avec le nom d’une référence sur l’objet à l’extérieur (exemple:
p1.x) -
La manipulation directe d’attributs en dehors de la classe est interdite (violation de l’encapsulation)
Invocation d’une méthode
-
Même syntaxe que pour les attributs mais avec la liste des paramètres (exemple:
p1.getAbscisse())
3.2.5. Destruction
-
Quand un objet n’est plus utilisé, il doit être retiré de la mémoire
-
La destruction des objets en Java est automatique
-
l’environnement d’exécution de Java supprime les objets lorsqu’il détermine qu’ils ne sont plus utilisés
-
un objet est éligible pour la destruction quand plus aucune référence n’est liée à lui
-
-
Ce processus de suppression s’appelle ramasse-miette (garbage collector)
-
Avant de détruire l’objet, le ramasse-miette invoque la méthode
protected void finalize()de l’objet-
utilisée pour restituer les ressources allouées par l’objet
-
finalizeest membre de la classeObject -
super.finalize()doit être d’appeler à la fin de la méthode
-
3.3. Les chaînes de caractères en Java
3.3.1. Les chaînes de caractères en Java
-
Java fournit trois classes pour les chaînes de caractères:
String,StringBufferetStringBuilder -
Stringest dédiée aux chaînes de caractères immuables, i.e. dont la valeur ne change pas -
StringBuilderest dédiée aux chaînes de caractères pouvant être modifiées (contexte mono-thread) -
StringBufferest dédiée aux chaînes de caractères pouvant être modifiées (contexte multi-threads)
3.3.2. Création d’une chaîne (String)
-
Une instance de
Stringreprésente une chaîne au format UTF-16 -
Une chaîne est souvent créée à partir d’un littéral de type chaîne (une suite de caractères entre guillemets)
-
quand Java rencontre un littéral de type chaîne, il crée un objet de type
Stringdont la valeur est le littéral
-
-
Une chaîne peut aussi être créée en utilisant l’un des constructeurs de
String
3.3.3. Quelques accesseurs de String
length()-
taille de la chaîne,
charAt(int)-
caractère à l’indice spécifié,
substring(int, int)-
extraction d’une sous-chaîne,
indexOf(…),lastIndexOf(…)-
recherche dans la chaîne
3.3.4. Autres usages de String
-
Un littéral chaîne peut être utilisé à tout endroit où un objet
Stringpeut l’être-
on peut invoquer des méthodes de
Stringsur un littéral chaîne
-
-
L’opérateur
+permet de concaténer des objets de typeString-
c’est le seul opérateur surchargé pour un objet en Java
-
-
Une chaîne peut être utilisée avec l’instruction
switch
3.3.5. Les classes StringBuilder et StringBuffer
-
Les instances disposent à peu prés des mêmes accesseurs que
String -
Quelques mutateurs:
append(…)-
ajout de caractères
delete(…)-
suppression de caractères
insert(…)-
insertion de caractères
-
StringBuilderest optimisée pour un environnement mono-thread -
StringBufferest à utiliser dans un contexte multi-threads
3.3.6. Exemple : Manipulation de chaînes de caractères
String source = "abcde";
int len = source.length();
StringBuilder dest = new StringBuilder(len);
for (int i = (len - 1); i >= 0; --i) {
dest.append(source.charAt(i));
}
assert dest.toString().equals("edcba") : dest;
3.4. Caractéristiques d’une classe
3.4.1. Catégories de méthodes
- Accesseur
-
permet de consulter l’état d’un objet
- Mutateur
-
modifie l’état d’un objet (doit préserver l’invariant)
- Constructeur
-
initialise un objet afin de le placer dans un état cohérent
- Destructeur
-
libère les ressources allouées par l’objet
3.4.2. Surcharge de méthode
-
La surcharge (ou polymorphisme ad hoc) d’une méthode consiste à définir plusieurs méthodes de même nom mais ayant des signatures différentes
-
Une opération n’est donc plus simplement identifiée par son nom mais également par le nombre, l’ordre et le type de ses arguments
-
Le choix de la méthode est résolu lors de la compilation
-
La surcharge est souvent utilisée pour définir plusieurs constructeurs de façon à initialiser les objets de différentes manières
-
Une alternative plus souple consiste à appliquer un modèle de conception Fabrique (Factory)
3.4.3. Accès aux membres
-
Une classe peut contrôler l’accès à ses membres (données ou fonctions)
- privé
-
accès limité à la classe
- protégé
-
accès limité à la classe et à ses sous-classes
- module
-
accès limité au module (package) contenant la classe
- public
-
accès non limité
3.4.4. Invariant de classe
| l'invariant d’une classe impose une contrainte sur l’état des instances de la classe. Chaque objet doit respecter les conditions imposées par l’invariant. |
-
L’invariant est établi lors de la création d’une instance
-
Il doit être vérifié avant l’exécution d’une opération publique
-
Chaque opération publique doit rétablir l’invariant avant de se terminer
-
L’invariant peut être violé temporairement lors de l’exécution d’une opération
-
Avec un langage de programmation, l’invariant peut être exprimé avec des assertions ou avec une construction spécifique
3.5. Les classes en Java
3.5.1. Définir une classe
| La définition d’une classe comporte deux parties: la déclaration et le corps de la classe |
/**
* Un bref commentaire.
* Un commentaire plus détaillé...
* @version janv. 2017
* @author Prénom NOM
*/
class NomClasse { // Déclaration de la classe
// Corps de la classe
}
-
La déclaration précise au compilateur un certain nombre d’informations sur la classe (son nom, …)
-
Le corps de la classe contient les attributs et les méthodes (les membres) de la classe
3.5.2. Déclarer des attributs
-
La déclaration d’un attribut spécifie son nom et son type
/** Description de l'attribut. */
Type nom;
/** Description de l'attribut. */
final Type nom;
-
L’initialisation des attributs se fait dans un constructeur
-
finalest optionnel et permet de déclarer un attribut qui ne pourra être affecté qu’une unique fois
3.5.3. Pseudo-attribut this
-
Chaque classe possède un attribut privé particulier nommé
thisqui référence l’objet courant -
Cette attribut est maintenu par le système et ne peut pas être modifié par le programmeur
-
thisn’est accessible que dans le corps de la classe -
Usage
-
passer l’objet courant en paramètre d’une méthode (
unObjet.uneMethode(this)) -
lever certaines ambiguïtés à propos des membres (
this.centre = centre) -
invoquer un autre constructeur dans un constructeur (
this(centre, 1))
-
3.5.4. Exemple : les attributs d’un cercle
package fr.uvsq.info.poo.classes;
import fr.uvsq.info.poo.coo.Point2D;
/**
* Un cercle en deux dimensions.
*
* @version jan. 2017
* @author Stéphane Lopes
*
*/
class Cercle2D {
/** Le centre du cercle. */
private Point2D centre;
/** Le rayon du cercle */
private final double rayon;
3.5.5. Définir des méthodes
-
La définition d’une méthode comporte deux parties: la déclaration et le corps (l'implémentation) de la méthode
/**
* Brêve description de la méthode.
* Une description plus longue...
* @param param1 description du paramêtre
* @param ...
* @return description de la valeur de retour
*/
TypeRetour nomMethode(listeDeParametres) { // Déclaration
// Corps de la méthode
}
-
TypeRetourest le type de la valeur retournée ouvoidsi aucune valeur n’est retournée -
Dans le corps de la méthode, on utilise l’opérateur
returnpour renvoyer une valeur -
Un constructeur a le même nom que sa classe et ne possède pas de type de retour
-
bien que l’on puisse initialiser les attributs directement (affection lors de leur déclaration ou bloc d’initialisation), il est préférable de toujours le faire dans le constructeur (plus de souplesse, initialisations à un seul endroit)
-
3.5.6. Paramètres de méthode
-
listeDeParamètresest une liste de déclarations de variables séparées par des virgules-
un paramètre peut être vu comme une variable locale à la méthode
-
finalpeut préfixer la déclaration si le paramètre ne doit pas être modifié
-
-
Le passage de paramètres se fait par valeur
-
la valeur d’un paramètre d’un type primitif ne peut pas être modifiée
-
la valeur d’une référence ne peut pas être modifiée mais l’objet référencé peut l’être (comme avec un pointeur en C)
-
3.5.7. Exemple : les constructeurs de la classe Cercle2D
/**
* Initialise un cercle avec un centre et un rayon.
* @param centre Le centre.
* @param rayon Le rayon.
*/
public Cercle2D(final Point2D centre, final double rayon) {
this.centre = centre;
this.rayon = rayon;
}
/**
* Initialise un cercle centré à l'origine et de rayon 1
*/
public Cercle2D() {
this(new Point2D(), 1.0);
}
3.5.8. Exemple : les accesseurs de la classe Cercle2D
/**
* Renvoie le centre du cercle.
* @return le centre du cercle.
*/
public Point2D getCentre() {
return centre;
}
/**
* Renvoie le rayon du cercle.
* @return le rayon du cercle.
*/
public double getRayon() {
return rayon;
}
3.5.9. Exemple : le mutateur de la classe Cercle2D
/**
* Translate le cercle.
* @param dx deplacement en abscisse.
* @param dy deplacement en ordonnees.
*/
public void translate(final double dx, final double dy) {
centre.translate(dx, dy);
}
3.5.10. Contrôle d’accès aux membres en Java
-
Le contrôle de l’accès aux membres permet de spécifier l’interface d’un classe
-
Le niveau d’accès est précisé en ajoutant un mot-clé devant la déclaration du membre (attribut ou méthode)
-
Il peut prendre l’une des valeurs
private, public, protectedou être absent
| Niveau | Classe | Module | Sous-classe | Extérieur |
|---|---|---|---|---|
|
X |
|||
aucun |
X |
X |
||
|
X |
X |
X |
|
|
X |
X |
X |
X |
-
La restriction d’accès s’applique au niveau de la classe et non pas de l’objet
-
Les attributs sont déclarés
privatepour respecter l’encapsulation
3.5.11. La classe Cercle2D dans son ensemble
// tag::cercle-attr[]
package fr.uvsq.info.poo.classes;
import fr.uvsq.info.poo.coo.Point2D;
/**
* Un cercle en deux dimensions.
*
* @version jan. 2017
* @author Stéphane Lopes
*
*/
class Cercle2D {
/** Le centre du cercle. */
private Point2D centre;
/** Le rayon du cercle */
private final double rayon;
// end::cercle-attr[]
// tag::cercle-cons[]
/**
* Initialise un cercle avec un centre et un rayon.
* @param centre Le centre.
* @param rayon Le rayon.
*/
public Cercle2D(final Point2D centre, final double rayon) {
this.centre = centre;
this.rayon = rayon;
}
/**
* Initialise un cercle centré à l'origine et de rayon 1
*/
public Cercle2D() {
this(new Point2D(), 1.0);
}
// end::cercle-cons[]
// tag::cercle-access[]
/**
* Renvoie le centre du cercle.
* @return le centre du cercle.
*/
public Point2D getCentre() {
return centre;
}
/**
* Renvoie le rayon du cercle.
* @return le rayon du cercle.
*/
public double getRayon() {
return rayon;
}
// end::cercle-access[]
// tag::cercle-mut[]
/**
* Translate le cercle.
* @param dx deplacement en abscisse.
* @param dy deplacement en ordonnees.
*/
public void translate(final double dx, final double dy) {
centre.translate(dx, dy);
}
// end::cercle-mut[]
/**
* Retourne une chaine decrivant le cercle.
* @return la representation textuelle du cercle.
*/
@Override
public String toString() {
return String.format("[(%f, %f), %f]", centre.getAbscisse(), centre.getOrdonnee(), rayon);
}
}
3.5.12. Exercice : manipulation d’objets et définition de classes
Pour cet exercice, on reprendra les spécifications obtenues lors de l’exercice précédent.
-
Soit le terrain suivant (M = mur, R = robot, N = nord, O = ouest).
-
Écrire les instructions Java permettant de créer ce terrain puis de déplacer le robot (0, 0) en (1, 1).
-
Écrire en Java la classe
Robot.
3.6. Métaclasse et membres de classe
3.6.1. Métaclasse
-
La relation qui existe entre objet et classe est une relation d’instanciation
-
De même, on peut considérer une classe comme une instance de classe de plus "haut niveau", dite métaclasse
-
Cette notion permet d’introduire des méthodes de classe et des attributs de classe, i.e. des membres associés à la classe et non pas à une instance particulière
-
Une méthode de classe peut être invoquée sans instance particulière
-
Un attribut de classe est associé à la classe elle-même et non pas à une instance particulière
3.7. Membres de classe en Java
3.7.1. Membres de classe en Java
-
Un membre de classe se déclare avec le mot-clé
static -
L’accès à un membre de classe se fait par l’intermédiaire de la classe
-
Pour un attribut de classe, le système alloue un espace mémoire pour un attribut par classe (et non pas un attribut par instance)
-
Un attribut de classe est souvent utilisé pour définir une constante (
static final)
public static final double E = 2.718281828459045d;
public static final double PI = 3.141592653589793d;
-
L’initialisation d’un attribut de classe peut se faire directement ou en utilisant un bloc d’initialisation statique
-
Un bloc d’initialisation statique est un bloc de code Java classique commençant par le mot-clé
staticet placé dans le corps de la classe -
Une méthode de classe ne peut pas accéder aux attributs d’instance (pas de
this)
3.7.2. Exemple : compter les instances de cercle
/**
* Un cercle en deux dimensions.
*
* @author Stephane Lopes
* @version jan. 2017
*/
class Cercle2DWithCpt extends Cercle2D {
/**
* Le nombre d'instances de Cercle2DWithCpt
*/
static int nbInstances = 0;
/**
* Initialise un cercle avec un centre et un rayon.
*
* @param centre Le centre.
* @param rayon Le rayon.
*/
public Cercle2DWithCpt(Point2D centre, double rayon) {
super(centre, rayon);
++nbInstances;
}
/**
* Retourne le nombre d'instances de la classe.
*
* @return le nombre d'instances.
*/
public static int getNbInstances() {
return nbInstances;
}
/**
* Décrémentation du nombre d'instances quand l'objet est détruit.
*/
@Override
protected void finalize() throws Throwable {
--nbInstances;
super.finalize();
}
3.7.3. Exemple : invoquer une méthode de classe
assert Cercle2DWithCpt.getNbInstances() == 0 :
Cercle2DWithCpt.getNbInstances();
// Creation des cercles
Cercle2DWithCpt c1 = new Cercle2DWithCpt(new Point2D(2.0, 4.0),
5.0);
Cercle2DWithCpt c2 = new Cercle2DWithCpt(new Point2D(1.0, 2.0),
4.0);
Cercle2DWithCpt c3 = new Cercle2DWithCpt(new Point2D(3.0, 4.0),
2.0);
assert Cercle2DWithCpt.getNbInstances() == 3 :
Cercle2DWithCpt.getNbInstances();
// Simple liaison
Cercle2DWithCpt unAutreC3 = c3;
assert Cercle2DWithCpt.getNbInstances() == 3 :
Cercle2DWithCpt.getNbInstances();
// Suppression d'un instance
c1 = null;
System.gc();
Thread.sleep(1000);
assert Cercle2DWithCpt.getNbInstances() == 2 :
Cercle2DWithCpt.getNbInstances();
3.8. Le programme principal en Java
3.8.1. Rôle du programme principal
-
Un système OO en cours d’exécution est une collection d’objets qui interagissent
-
Le lancement du programme doit donc permettre d’instancier ces objets
-
en général, le programme principal se limite à créer un objet application qui se charge d’instancier les autres objets
-
3.8.2. Le programme principal en Java : la méthode main
-
Le point d’entrée d’une application Java est une méthode de classe nommée
main -
Lors de l’exécution, l’interpréteur Java est invoqué avec le nom d’une classe qui doit implémenter une méthode
main -
La déclaration de la méthode
mainestpublic static void main(String[] args) -
Le paramètre de
mainest un tableau de chaînes de caractères contenant les arguments de ligne de commande passés lors de l’appel du programme -
On limite en général le code se trouvant dans le
mainau strict minimum: création d’un objet application et invocation d’une méthode
3.8.3. Exemple : le programme principal en Java (avec une énumération)
package fr.uvsq.info.poo.classes;
/**
* Représente l'application.
*
* @version jan. 2017
* @author Stéphane Lopes
*
*/
enum ApplicationVide {
ENVIRONNEMENT;
/*
* Méthode principale du programme.
* @param args les arguments de ligne de commande
*/
public void run(String[] args) {
// ...
}
/*
* Point d'entrée du programme.
* @param args les arguments de ligne de commande
*/
public static void main(String[] args) {
ENVIRONNEMENT.run(args);
}
}
3.9. Énumération en Java
3.9.1. Complément sur le type énuméré
-
Un type énuméré en Java est en fait une classe dont les instances sont connues lors de la compilation
-
Les constantes d’un type énuméré peuvent être utilisées partout où un objet peut l’être
-
Un type énuméré peut donc contenir des méthodes et des attributs
-
De plus, le compilateur ajoute automatiquement certaines méthodes
-
values()retourne un tableau contenant les constantes dans l’ordre de leur déclaration -
un type énuméré hérite implicitement de la classe
Enum
-
3.9.2. Exemple : un type énuméré pour les planètes
public enum Planet {
MERCURY (3.303e+23, 2.4397e6),
VENUS (4.869e+24, 6.0518e6),
EARTH (5.976e+24, 6.37814e6),
MARS (6.421e+23, 3.3972e6),
JUPITER (1.9e+27, 7.1492e7),
SATURN (5.688e+26, 6.0268e7),
URANUS (8.686e+25, 2.5559e7),
NEPTUNE (1.024e+26, 2.4746e7);
private final double mass; // in kilograms
private final double radius; // in meters
Planet(double mass, double radius) {
this.mass = mass;
this.radius = radius;
}
private double mass() { return mass; }
private double radius() { return radius; }
// universal gravitational constant (m3 kg-1 s-2)
public static final double G = 6.67300E-11;
double surfaceGravity() {
return G * mass / (radius * radius);
}
double surfaceWeight(double otherMass) {
return otherMass * surfaceGravity();
}
public static void main(String[] args) {
if (args.length != 1) {
System.err.println("Usage: java Planet <earth_weight>");
System.exit(-1);
}
double earthWeight = Double.parseDouble(args[0]);
double mass = earthWeight/EARTH.surfaceGravity();
for (Planet p : Planet.values())
System.out.printf("Your weight on %s is %f%n",
p, p.surfaceWeight(mass));
}
}
3.10. Généricité
3.10.1. Généricité
-
La généricité permet de paramétrer une classe par un ou plusieurs paramètres formels (généralement des types)
-
La généricité permet de définir une famille de classes, chaque classe étant instanciée lors du passage des paramètres effectifs
-
Une classe paramétrée (ou générique) est donc une métaclasse particulière
-
Il peut être souhaitable de limiter les paramètres possibles : la généricité est alors contrainte
-
Utiliser un type paramétré améliore la vérification de type lors de la compilation
-
Cette notion est orthogonale au paradigme OO : on parle de programmation générique
3.11. Généricité en Java
3.11.1. Généricité en Java
-
Les paramètres formels de type sont placés entre “<” et “>”
-
Un paramètre effectif est obligatoirement une classe (pas un type primitif)
-
Le mécanisme implémentant la généricité en Java se nomme Type erasure
-
Ce mécanisme supprime toute trace de la généricité dans le bytecode ⇒ il n’existe pas d’information concernant la généricité à l’exécution
3.11.2. Classe générique en Java
-
Les paramètres formels de type sont placés entre “<” et “>” juste après le nom de la classe
class Cercle<T> {
//...
}
-
Le paramètre de type peut ensuite être utilisé comme tout autre type dans la définition de la classe
-
La déclaration d’une variable de ce type nécessite de passer le type effectif à la classe paramétrée
Cercle<Point2D> c = //...
-
la création d’une instance ne répète pas le type effectif (diamond notation)
Cercle<Point2D> c = new Cercle<>(/* ... */);
3.11.3. Exemple : définition de la classe générique Cercle
/**
* Un cercle générique.
* Ce cercle peut s'adapter au cas à 2 ou à 3 dimensions.
*
* @version jan. 2017
* @author Stephane Lopes
*
*/
class Cercle<T> {
/** Le centre du cercle. */
private T centre;
/** Le rayon du cercle */
private double rayon;
/**
* Initialise un cercle avec un centre et un rayon.
* @param centre Le centre.
* @param rayon Le rayon.
*/
public Cercle(T centre, double rayon) {
this.centre = centre;
this.rayon = rayon;
}
public T getCentre() {
return centre;
}
public double getRayon() {
return rayon;
}
3.11.4. Exemple : utilisation de la classe générique Cercle
// En 2 dimensions
Point2D p1 = new Point2D(1.0, 2.0);
Cercle<Point2D> c1 = new Cercle<Point2D>(p1, 3.0);
// Invocation d'une methode de Point2D
double xCentre = c1.getCentre().getAbscisse();
// En 3 dimensions
Point3D p2 = new Point3D(3.0, 4.0, 5.0);
Cercle<Point3D> c2 = new Cercle<Point3D>(p2, 3.0);
// Invocation d'une methode de Point3D
double zCentre = c2.getCentre().getZ();
3.11.5. Méthode générique en Java
-
Il est possible de déclarer des méthodes génériques
-
Le paramètre de type formel est alors précisé avant la déclaration
public static <T> T max(T o1, T o2) // ...
-
La portée de ce paramètre est alors restreinte à la méthode
-
L’invocation de la méthode peut préciser le type effectif ou se baser sur l'inférence de type
// Type effectif explicite
Integer i = uneClasse.<Integer>max(i1, i2);
// Type effectif déterminé par inférence de type
Integer i = uneClasse.max(i1, i2);
3.11.6. Généricité contrainte en Java
-
Il est possible d’imposer que le paramètre de type formel soit un sous-type d’un autre type avec le mot-clé
extends
public static <T extends Number> T max(T o1, T o2) // ...
-
Le mot-clé
superpermet d’imposer que le paramètre de type formel soit un super-type d’un autre type (exemple :<T super Number>) -
Il peut être nécessaire d’utiliser le caractère joker
?si le type effectif n’est pas connu
// Une boite qui peut contenir des nombres
Boite<? extends Number> b = //...
// Un boite qui peut contenir n'importe quoi
Boite<?> b = //...
-
En complément : The Java Tutorials - Guidelines for Wildcard Use
3.12. Exercices (Java)
|
3.12.1. Création d’une classe simple
L’objet de cet exercice est de réaliser une classe ChaineCryptee qui permettra de chiffrer/déchiffrer une chaîne de caractères (composée de lettres majuscules et d’espace).
Le chiffrement utilise une méthode par décalage.
La valeur du décalage représente la clé de chiffrement.
Par exemple, une clé de valeur trois transformera un 'A' en 'D'.
-
Créez la classe
ChaineCrypteeavec pour attribut la chaîne en clair et le décalage. -
Ajoutez un constructeur pour initialiser les instances à partir d’une chaîne en clair et du décalage.
-
Ajoutez la méthode
String decrypte()qui retourne la chaîne en clair. -
Ajoutez la méthode
String crypte()qui retourne la chaîne chiffrée. Vous pourrez utilisez pour cela la méthodedecaleCaractere(voir le listing ci-dessous). -
Comment se comporte votre classe si la chaîne passée au constructeur est
null? Vous pouvez utiliser le débogueur pour identifier le problème (s’il y a un problème) au niveau decrypte.-
si la méthode a échoué, modifiez la classe pour prendre en compte la chaîne
null, -
vérifiez avec le débogueur que le problème est réglé.
-
-
Faites afficher l’interface de la classe (la documentation
JavaDoc). Le comportement de votre classe en cas de chaînenulldoit être expliqué dans la documentation. -
Changez la représentation interne de la classe : seule la chaîne cryptée est stockée (plus la chaîne en clair).
-
effectuez les modifications nécessaires sans changer l’interface de la classe.
-
-
Ajoutez la possibilité d’initialiser une instance à partir d’une chaîne cryptée et d’un décalage. Pour éviter l’ambiguïté au niveau du constructeur, vous utiliserez le modèle de conception Fabrication. Pour cela,
-
créez les méthodes de classe
ChaineCryptee deCryptee(String, int)etChaineCryptee deEnClair(String, int), -
rendez le constructeur privé. La création des instances se fait maintenant à l’aide des deux méthodes de classe.
-
/**
* Décale un caractère majuscule.
* Les lettres en majuscule ('A' - 'Z') sont décalées de <code>decalage</code>
* caractères de façon circulaire. Les autres caractères ne sont pas modifiés.
*
* @param c le caractère à décaler
* @param decalage le décalage à appliquer
* @return le caractère décalé
*/
private static char decaleCaractere(char c, int decalage) {
return (c < 'A' || c > 'Z')? c : (char)(((c - 'A' + decalage) % 26) + 'A');
}
À la fin de cet exercice, la classe ChaineCryptee doit avoir la structure suivante
3.12.2. Classes et associations
On souhaite simuler le comportement d’un logiciel de discussion client/serveur. Pour pouvoir discuter, un client doit au préalable se connecter au serveur. Lorsque le client envoie un message au serveur, ce dernier le transmet à tous les clients avec le nom de l’émetteur.
Un diagramme de classes possible pour cet énoncé est donné par la figure suivante.
-
Donnez un diagramme de séquence UML décrivant le scénario suivant:
-
le serveur s est actuellement connecté avec les clients c1 et c2,
-
le client c3 se connecte à s,
-
le client envoie le message Bonjour aux clients connectés.
-
-
Implémentez les classes
ClientetServeur. -
déroulez le scénario de la question 1 dans Bluej.
3.12.3. Agrégation et composition
Un document est décrit par un titre, le nom de son auteur, l’année de publication et une liste de références sur d’autres documents (par exemple, des documents traitant du même sujet). Une bibliothèque gère un ensemble de documents.
Les opérations que l’on souhaite pouvoir effectuer sont les suivantes :
-
initialisation d’un document avec son titre, l’auteur et une année,
-
ajout d’une référence dans un document,
-
affichage (construire une chaîne de caractères le représentant) d’un document (avec ses références sous la forme titre, auteur),
-
ajout d’un document dans la bibliothèque,
-
recherche par titre d’un document dans la bibliothèque,
-
recherche des documents citant un document.
-
Modélisez cette énoncé à l’aide d’un diagramme de classe.
-
Implémentez ce modèle.
-
4. Héritage
4.1. Héritage en Java
4.1.1. Héritage en Java
-
On spécifie qu’une classe est une sous-classe d’une autre en utilisant
extendsdans la déclarationclass NomClasse extends NomSuperClasse -
Une classe ne peut avoir qu’une seule super-classe (pas d’héritage multiple)
-
Si
extendsn’est pas précisé, la classe hérite de la classeObject -
Une classe Java a une et une seule super-classe
-
Une classe déclarée
finalne peut plus être spécialisée
4.1.2. Héritage et membres
-
Une classe C hérite de sa super-classe S les attributs et méthodes qu’elle possède
-
tous les attributs de S font partie de l’état des instances de C
-
les méthodes publiques de S font partie de l’interface publique de C
-
-
Les attributs et méthodes d’une super-classe ne sont pas formcément accessibles
-
les attributs privées de S ne sont pas accessibles dans C
-
les méthodes non privées de S sont accessibles dans C
-
les constructeurs de S sont utilisables dans les constructeurs de C mais ne font pas partie de l’interface publique de C
-
4.1.3. Masquage de membres
-
Une classe peut masquer un membre de sa super-classe si elle possède un membre de même nom (ou de même signature)
-
Le mot clé
superpermet d’accéder aux membres masqués d’une super-classe
4.1.5. Exemple : définition de la classe Rectangle2DPlein en Java
/**
* Un rectangle plein en deux dimensions.
*
* @author Stéphane Lopes
* @version nov. 2008
*/
class Rectangle2DPlein extends Rectangle2D {
/**
* La couleur de remplissage
*/
private Color couleur;
/**
* Initialise le rectangle plein.
*
* @param supGauche Le coin supérieur gauche.
* @param infDroit Le coin inférieur droit.
* @param couleur La couleur de remplissage.
*/
public Rectangle2DPlein(Point2D supGauche,
Point2D infDroit,
Color couleur) {
super(supGauche, infDroit);
assert couleur != null;
this.couleur = couleur;
}
/**
* Renvoie la couleur.
*
* @return la couleur.
*/
public Color getCouleur() {
return couleur;
}
-
extendsexprime l’héritage -
seule les attributs supplémentaires sont déclarés dans la sous-classe
-
dans le constructeur,
superpermet d’appeler le constructeur de la super-classe -
seule les méthodes supplémentaires sont définies dans la sous-classe
4.1.6. Exemple : utilisation de la classe Rectangle2DPlein
// Déclaration et création d'un rectangle rouge
Rectangle2DPlein rp = new Rectangle2DPlein(new Point2D(1.0, 2.0),
new Point2D(3.0, 0.0),
Color.RED);
assert rp.getCouleur() == Color.RED;
// Déclaration d'un rectangle et
// liaison avec un rectangle plein
Rectangle2D r = new Rectangle2DPlein(new Point2D(1.0, 2.0),
new Point2D(2.0, 1.0),
Color.YELLOW);
assert r.getLargeur() == 1;
-
L’affectation d’une instance de
Rectangle2DPleinà une référence sur unRectangle2Drespecte le principe de substitution de Liskov -
À partir de
r1, l’accès àgetCouleurest impossible (échoue à la compilation) cargetCouleurne fait pas partie de l’interface deRectangle2D
4.1.7. Application : l’héritage
Deux nouveaux types de robot sont créés :
-
Les transporteurs qui peuvent ramasser un objet, le transporter et le déposer,
-
Les destructeurs qui peuvent détruire ce qui se trouve sur la case devant eux.
Soit le terrain suivant (M = mur, T = transporteur, D = destructeur, N = nord, E = est, S = sud, O = ouest).
-
Donner un diagramme de classes représentant les nouveaux robots
-
Écrire les instructions permettant de créer ce terrain puis de déplacer l’objet se trouvant en (0, 0) en (2, 2).
-
Donner l’implémentation des deux classes
TransporteuretDestructeur.
4.2. Polymorphisme en Java
4.2.1. Redéfinition de méthode
-
Une sous-classe peut redéfinir (override) une ou plusieurs méthodes de sa super-classe
-
La redéfinition (overriding) consiste à définir dans une sous-classe, une méthode ayant même signature et même type de retour qu’une méthode de la super-classe
-
la méthode de la super-classe est alors masquée
-
il est toujours possible d’appeler la méthode redéfinie en utilisant le mot-clé
super
-
4.2.2. Redéfinition et polymorphisme
-
Le polymorphisme est le mécanisme permettant de sélectionner la méthode redéfinie appropriée
-
La redéfinition ne permet plus au compilateur de sélectionner la méthode adéquat
-
C’est le type de l’objet (et non pas de la référence) qui permettra de déterminer la méthode à invoquer et ce type ne peut être connu qu’au moment de l’exécution
4.2.3. Compléments sur la redéfinition de méthode
-
La déclaration de la méthode redéfinie est toujours précédée de l’annotation
@Override -
Le contrôle d’accès peut être relaxé lors de la redéfinition
-
Une méthode déclarée
finalne peut pas être redéfinie -
Une méthode de classe ne peut pas être redéfinie
4.2.5. Exemple : la classe Rectangle2D
/**
* Un rectangle en deux dimensions.
* Les côtés du rectangle sont toujours parallèles aux axes.
*
* @version jan. 2017
* @author Stéphane Lopes
*
*/
class Rectangle2D extends FigureFermee2D implements Cloneable {
/** Coordonnées du coin supérieur gauche */
private Point2D orig;
/** Coordonnées du coin inférieur droit */
private Point2D fin;
/**
* Initialise le rectangle.
* @param supGauche Le coin supérieur gauche.
* @param infDroit Le coin inférieur droit.
*/
public Rectangle2D(Point2D supGauche, Point2D infDroit) {
assert supGauche.getX() <= infDroit.getX() &&
supGauche.getY() >= infDroit.getY();
orig = supGauche;
fin = infDroit;
}
public Point2D getSupGauche() { return orig; }
public double getLargeur() {
return fin.getX() - orig.getX();
}
public double getHauteur() {
return orig.getY() - fin.getY();
}
/**
* Retourne une description du rectangle.
* @return la description.
*/
public String getDesc() {
return String.format("O = %s L = %s, H = %s", orig, getLargeur(), getHauteur());
}
4.2.6. Exemple : la classe Rectangle2DPlein
/**
* Un rectangle plein en deux dimensions.
*
* @author Stéphane Lopes
* @version nov. 2008
*/
class Rectangle2DPlein extends Rectangle2D {
/**
* La couleur de remplissage
*/
private Color couleur;
/**
* Initialise le rectangle plein.
*
* @param supGauche Le coin supérieur gauche.
* @param infDroit Le coin inférieur droit.
* @param couleur La couleur de remplissage.
*/
public Rectangle2DPlein(Point2D supGauche,
Point2D infDroit,
Color couleur) {
super(supGauche, infDroit);
assert couleur != null;
this.couleur = couleur;
}
/**
* Renvoie la couleur.
*
* @return la couleur.
*/
public Color getCouleur() {
return couleur;
}
4.2.7. Exemple : utilisation du polymorphisme
// Création d'un tableau de références sur des Rectangle2D
final int NB_RECTANGLES = 2;
Rectangle2D[] figures = new Rectangle2D[NB_RECTANGLES];
// Un rectangle
figures[0] = new Rectangle2D(new Point2D(0.0, 5.0),
new Point2D(2.0, 2.0));
// Un rectangle plein
figures[1] = new Rectangle2DPlein(new Point2D(1.0, 3.0),
new Point2D(3.0, 2.0),
Color.BLUE);
// getDesc() de Rectangle2D
assert figures[0].getDesc().equals("O = (0.0, 5.0) L = 2.0 H = 3.0");
// getDesc() de Rectangle2DPlein
assert figures[1].getDesc().equals("O = (1.0, 3.0) L = 2.0 H = 1.0, couleur : (0, 0, 255)");
4.2.8. Application : le polymorphisme et la redéfinition
Une amélioration importante a été apportée aux robots: ils sont maintenant programmables. Chaque type de robot possède son propre comportement. Quand ils en reçoivent l’ordre, ils exécutent l’action programmée (un ensemble d’instructions élémentaires). Plusieurs robots de types différents se trouvent sur le terrain. On veut pouvoir déclencher l’exécution du programme de l’ensemble des robots.
-
Donner un diagramme de classe des robots
-
Implémenter les changements dans les classes
Robot,TransporteuretDestructeur -
Écrire un programme déclenchant l’exécution de l’action pour l’ensemble des robots
4.3. La classe Object
4.3.1. La classe Object
-
La classe
Objetdéfinit et implémente le comportement dont chaque classe Java a besoin -
C’est la plus générale des classes Java
-
Chaque classe Java hérite directement ou indirectement de
Object(tout objet y compris les tableaux implémente les méthodes deObject)
4.3.2. Les méthodes de la classe Object
-
Certaines méthodes de
Objectpeuvent être redéfinies pour s’adapter à la sous-classe -
protected Object clone()permet de dupliquer un objet -
boolean equals(Object obj)permet de tester l’égalité de deux objets etint hashCode()de renvoyer une valeur de hashage-
Object.equalsteste l’identité -
equalsethashCodedoivent être redéfinies ensembles
-
-
protected void finalize()représente le destructeur d’un objet -
String toString()retourne une chaîne représentant l’objet-
toStringest très utile pour le débogage ⇒ toujours la redéfinir
-
-
Autres méthodes
-
Class getClass()retourne un objet de typeClassreprésentant la classe de l’objet-
la classe
Classest par exemple utile pour créer des objets dont la classe n’est pas connu à la compilation
-
-
quelques méthodes pour les threads
-
4.3.3. Exemple : redéfinition de la méthode toString() de Rectangle2D
/**
* Retourne une chaîne représentant l'objet.
* @return la chaîne.
*/
@Override
public String toString() {
return String.format("O = %s L = %s, H = %s", orig, getLargeur(), getHauteur());
}
4.3.4. Exemple : redéfinition de la méthode toString() de Rectangle2DPlein
/**
* Retourn une chaîne représentant l'objet.
*
* @return la chaîne.
*/
@Override
public String toString() {
return String.format("%s, couleur : %s", super.getDesc(), couleur);
}
// Test de toString
assert figures[0].toString().equals("O = (0.0, 5.0) [5.0, 0.0] L = 2.0 H = 3.0");
assert figures[1].toString().equals("O = (1.0, 3.0) [3.1622776601683795, 0.3217505543966422] L = 2.0 H = 1.0 couleur = java.awt.Color[r=0,g=0,b=255]");
4.3.5. Copie d’objets
-
L’opération de copie peut avoir différentes sémantiques
-
copie profonde (deep copy)
-
copie superficielle (shallow copy)
-
-
La copie peut être obtenue de plusieurs manières
-
par un constructeur de copie
-
par une méthode de classe (méthode de fabrication)
-
par clonage (implémentation de l’interface
Cloneableet redéfinition de la méthodeObject.clone)
-
-
L’usage de
cloneest déconseillée-
Copy Constructor versus Cloning, Josh Bloch, 2002
-
Java Cloning - Copy Constructor versus Cloning, Naresh Joshi, 2017
-
clone() vs copy constructor vs factory method?, stackoverflow, 2009
-
4.3.6. Egalité d’objets : la méthode equals
-
La méthode
boolean equals(Object o)teste l’égalité de deux objets -
La méthode
equalsde la classeObjectse contente de tester l’égalité des références des objets, i.e. l’identité -
Il est donc en général nécessaire de redéfinir
equalspour le test d’égalité -
Rappel: l’opérateur
==teste l’identité de ses opérandes, i.e. l’égalité des références
4.3.7. Egalité d’objets : contraintes de equals
-
equalsimplémente une relation d’équivalence pour des références d’objet non nulles-
x.equals(x) == true -
x.equals(y) == truesi et seulement siy.equals(x) == true -
si
x.equals(y) == trueety.equals(z) == truealorsx.equals(z) == true -
x.equals(null) == false
-
-
Toute classe qui redéfinit
equalsdoit également redéfinirhashCode()-
si deux objets sont égaux au sens de
equalsalorshashCodedoit produire le même résultat pour les deux objets
-
4.3.8. Exemple : égalité de rectangles
package fr.uvsq.info.poo.inheritance;
import javafx.geometry.Point2D;
// tag::rect[]
/**
* Un rectangle en deux dimensions.
* Les côtés du rectangle sont toujours parallèles aux axes.
*
* @version jan. 2017
* @author Stéphane Lopes
*
*/
class Rectangle2D extends FigureFermee2D implements Cloneable {
/** Coordonnées du coin supérieur gauche */
private Point2D orig;
/** Coordonnées du coin inférieur droit */
private Point2D fin;
/**
* Initialise le rectangle.
* @param supGauche Le coin supérieur gauche.
* @param infDroit Le coin inférieur droit.
*/
public Rectangle2D(Point2D supGauche, Point2D infDroit) {
assert supGauche.getX() <= infDroit.getX() &&
supGauche.getY() >= infDroit.getY();
orig = supGauche;
fin = infDroit;
}
public Point2D getSupGauche() { return orig; }
public double getLargeur() {
return fin.getX() - orig.getX();
}
public double getHauteur() {
return orig.getY() - fin.getY();
}
/**
* Retourne une description du rectangle.
* @return la description.
*/
public String getDesc() {
return String.format("O = %s L = %s, H = %s", orig, getLargeur(), getHauteur());
}
// end::rect[]
// tag::rect-tostring[]
/**
* Retourne une chaîne représentant l'objet.
* @return la chaîne.
*/
@Override
public String toString() {
return String.format("O = %s L = %s, H = %s", orig, getLargeur(), getHauteur());
}
// end::rect-tostring[]
/**
* Retourne une copie "profonde" de l'objet.
* @return la copie.
*/
@Override
public Object clone() throws CloneNotSupportedException {
Rectangle2D r = (Rectangle2D)super.clone();
r.orig = new Point2D(orig.getX(), orig.getY());
r.fin = new Point2D(fin.getX(), fin.getY());
return r;
}
// tag::rect-equals[]
/**
* Teste l'égalité de deux rectangles.
* @param obj le rectangle à comparer.
* @return true si les objets sont égaux.
*/
@Override
public boolean equals(Object obj) {
if (obj instanceof Rectangle2D) {
Rectangle2D r = (Rectangle2D)obj;
return orig.equals(r.orig) && fin.equals(r.fin);
}
return false;
}
/**
* Retourne une valeur de hashage pour l'objet.
* @return la valeur de hashage.
*/
@Override
public int hashCode() {
return orig.hashCode() ^ fin.hashCode();
}
// end::rect-equals[]
// tag::rect-translate[]
/**
* Translate le rectangle.
* @param dx déplacement en abscisse.
* @param dy déplacement en ordonnées.
*/
@Override
public void translate(double dx, double dy) {
orig.add(dx, dy);
fin.add(dx, dy);
}
// end::rect-translate[]
}
4.3.9. Exemple : contraintes de equals
Rectangle2D r1 = new Rectangle2D(new Point2D(0.0, 5.0),
new Point2D(2.0, 2.0));
Rectangle2D r2 = new Rectangle2D(new Point2D(0.0, 5.0),
new Point2D(2.0, 2.0));
Rectangle2D r3 = new Rectangle2D(new Point2D(0.0, 5.0),
new Point2D(2.0, 2.0));
assert r1.equals(r1); // Réflexivité
assert r1.equals(r2) && r2.equals(r1); // Symétrie
assert r1.equals(r2) && r2.equals(r3) &&
!r1.equals(r3) == false; // Transitivité
assert r1.equals(null) == false;
assert r1.hashCode() == r2.hashCode();
4.4. Classe abstraite en Java
4.4.1. Classe abstraite en Java
-
Une classe est spécifiée abstraite en ajoutant le mot-clé
abstractdans sa déclaration -
L’instanciation d’une telle classe est alors refusée par le compilateur
-
Une classe abstraite contient généralement des méthodes abstraites, i.e. qui ne possèdent pas d’implémentation
-
une classe abstraite peut cependant ne pas avoir de méthodes abstraites
-
-
Une méthode est déclarée abstraite en utilisant le mot-clé
abstractlors de sa déclaration -
Toute sous-classe non abstraite d’une classe abstraite doit redéfinir les méthodes abstraites de cette classe
-
Une classe possédant des méthodes abstraites est obligatoirement abstraite
4.4.3. Exemple : la classe abstraite FigureFermee2D
package fr.uvsq.info.poo.inheritance;
/**
* Une figure fermée.
*
* @version jan. 2017
* @author Stéphane Lopes
*
*/
abstract class FigureFermee2D {
/**
* Translate la figure.
* @param dx déplacement en abscisse.
* @param dy déplacement en ordonnée.
*/
public abstract void translate(double dx, double dy);
}
4.4.4. Exemple : redéfinition du déplacement
/**
* Translate le rectangle.
* @param dx déplacement en abscisse.
* @param dy déplacement en ordonnées.
*/
@Override
public void translate(double dx, double dy) {
orig.add(dx, dy);
fin.add(dx, dy);
}
/**
* Translate le cercle.
*
* @param dx déplacement en abscisse.
* @param dy déplacement en ordonnées.
*/
@Override
public void translate(double dx, double dy) {
centre.add(dx, dy);
}
4.4.5. Exemple : utilisation de la classe abstraite FigureFermee2D
// Création du tableau de références
final int NB_FIGURES = 4;
FigureFermee2D[] figures = new FigureFermee2D[NB_FIGURES];
// Création des formes
figures[0] = new Rectangle2D(new Point2D(0.0, 5.0),
new Point2D(2.0, 2.0));
figures[1] = new Cercle2D(new Point2D(1.0, 2.0), 3.0);
figures[2] = new Rectangle2D(new Point2D(5.0, 5.0),
new Point2D(7.0, 3.0));
figures[3] = new Cercle2D(new Point2D(4.0, 5.0), 2.0);
// Réalise une translation de la figure
for (int i = 0; i < figures.length; ++i) {
figures[i].translate(1.0, 2.0);
}
4.4.6. Application : les classes abstraites
On souhaite maintenant ajouter sur le terrain un type de case sensible à la charge (pont par exemple). Chaque élément mobile (robot ou objet transportable) devra donc posséder un poids. Le poids d’une instance de robot sera toujours de 1 unité. Une instance de destructeur aura un poids de 2 unités de plus que le robot. Une instance de transporteur aura un poids de 1 unités de plus que le robot auquel il faudra ajouter le poids de l’objet transporté. Le poids par défaut des objets transportables est de 1 unité mais chaque objet pourra avoir un poids différent précisé lors de sa création. On souhaite pouvoir connaître le poids de tout élément se trouvant sur le terrain.
-
Modéliser la prise en compte du poids au niveau des éléments mobiles
-
Implémenter les classes correspondantes
4.5. La classe Number
4.5.1. La classe Number
-
La classe
Numberest une classe abstraite de la librairie Java -
Elle définit le comportement commun aux classes pour la gestion des nombres (les conversions)
-
Elle possède plusieurs sous-classes
-
les adaptateurs :
Byte,Double,Float,Integer,Long,Short -
BigDecimal,BigInteger
-
-
Ces classes offrent une vue "objet" des types primitifs
La plupart des fonctions arithmétiques sont des méthodes de classe de la classe Math.
|
4.5.2. autoboxing/autounboxing
-
Ce mécanisme permet d’éviter la conversion manuelle entre type primitif et adaptateur
-
C’est simplement une facilité d’écriture (sucre syntaxique)
Integer i = 12; // à la place de : Integer i = Integer.valueOf(12);
int n = i; // à la place de : int n = i.intValue();
4.5.3. Les adaptateurs
-
Il existe d’autres adaptateurs :
Boolean,Character,Void -
Les adaptateurs sont des exemples du pattern de conception Adaptateur
4.6. Interface
4.6.1. Du point de vue des types
-
Une interface regroupe uniquement des signatures d’opérations et des déclarations de constantes mais aucune implémentation
-
Une interface permet donc de définir un type
-
Les interfaces peuvent être organisées en hiérarchies
-
Un lien entre interface est une relation de sous-typage
4.6.2. Du point de vue des services
-
Une interface fournit une vue totale ou partielle d’un ensemble de services offerts par une classe (un composant)
-
Une interface est analogue à un protocole de comportement (un contrat sur un comportement)
-
Une classe peut implémenter une ou plusieurs interfaces
-
Une interface est formellement équivalente à une classe abstraite ne possédant que des méthodes abstraites
4.6.3. Utilisation d’une interface
-
Permet à des objets d’interagir même s’ils ne sont pas en relation
-
Permet de capturer des similarités entre classes non reliées sans définir artificiellement une relation
-
Permet de déclarer des méthodes qu’une ou plusieurs classes doivent implémenter
-
Permet de révéler l’interface de programmation d’un objet sans révéler sa classe
4.6.5. Définition d’une interface en Java
-
La définition d’une interface comporte une déclaration et un corps
interface UneInterface extends UneSecondeInterface, UneAutreInterface {
final String uneChaine = "abcde";
final double unDouble = 123.456;
void uneMethode(int unEntier, String uneChaine);
}
-
Une interface peut avoir plusieurs super-interfaces
-
Toutes les méthodes de l’interface sont implicitement
publicetabstract -
Toutes les constantes de l’interface sont implicitement
public,static, etfinal
4.6.6. Utilisation d’une interface
-
Pour déclarer une classe qui implémente une ou plusieurs interfaces, on ajoute
implements ListeInterfacesdans sa déclaration (après la clauseextendssi elle existe) -
La classe doit alors implémenter toutes les méthodes de l’interface ou être déclarée abstraite
-
Une interface est utilisée comme un type (par exemple comme paramètre d’une méthode)
4.6.7. Exemple : définir l'ordre naturel des objets (interface Comparable)
// Comparable est une interface de la librairie Java
/**
* This interface imposes a total ordering on the objects
* of each class that implements it. ...
*/
interface Comparable<T> {
/**
* Compares this object with the specified object for order. ...
*/
public int compareTo(T o);
}
// ...
class Rectangle2D implements Comparable<Rectangle2D> {
// ...
public int compareTo(Rectangle2D o) {
// Code pour la comparaison
}
// ...
}
4.7. Exercices (Java)
|
4.7.1. Héritage et polymorphisme
On souhaite réaliser une application pour gérer une collection de CD et de DVD. Un CD possède un titre, le nom de l’artiste ou du groupe, et le nombre de titres. Un DVD possède un titre, un réalisateur et une année de sortie.
L’application devra permettre de :
-
créer des documents (CD ou DVD),
-
consulter les informations d’un document,
-
ajouter un document dans la collection,
-
lister les documents de la collection,
-
rechercher les documents contenant un mot clé dans le titre ou dans le groupe/réalisateur.
-
Donner un diagramme de classe UML modélisant ce problème.
-
Proposer une implémentation de cette modélisation.
-
4.7.2. Création d’une classe respectant les conventions du langage Java
L’objectif de cet exercice est de réaliser une classe immuable Fraction qui représente un nombre rationnel.
Un exemple d’interface pour une telle classe est donné par la classe Fraction de la bibliothèque Apache Commons Math.
Une fraction comporte un numérateur et un dénominateur (nombres entiers).
| Vous respecterez les conventions Java quand cela est approprié (égalité, conversion, comparaison, …). |
Implémentez la classe en fournissant l’interface suivante:
-
initialisation avec (i) un numérateur et un dénominateur, (ii) juste avec le numérateur (dénominateur égal à 1) ou (iii) sans argument (numérateur égal 0 et dénominateur égal à 1),
-
les constantes ZERO (0, 1) et UN (1, 1),
-
consultation du numérateur et du dénominateur,
-
consultation de la valeur sous la forme d’un nombre en virgule flottante (
double), -
addition de deux fractions,
-
test d’égalité entre fractions (deux fractions sont égales si elles représentent la même fraction réduite),
-
conversion en chaîne de caractères,
-
comparaison de fractions selon l’ordre naturel.
4.7.3. Héritage, polymorphisme et classe abstraite
On veut simuler le fonctionnement d’un système de fichiers. Un fichier est représenté par son nom et sa taille. Un répertoire est défini par son nom et peut contenir des fichiers et/ou des répertoires. La base de l’arborescence du système de fichier est le répertoire racine.
On veut pouvoir calculer la taille totale d’un répertoire.
-
Représenter sur un diagramme de classes UML une hiérarchie de classe modélisant ce problème. Le modèle de conception Composite peut être utilisé pour cela.
-
Proposer une implémentation de ce modèle.
-
Créer une arborescence de test et vérifier le calcul de la taille.
-
Modifier le programme pour qu’un répertoire ne puisse pas être ajouté à lui-même.
-
Modifier le programme pour qu’un répertoire ne puisse pas être ajouté comme descendant de lui-même.
4.7.4. Utilisation de la ligne de commande
Le but de cet exercice est de vous familiariser avec les outils Java en ligne de commande.
-
Créer le répertoire
FileSystemcontenant les sous-répertoiressrcetclasses. -
Placer les fichiers
.javade l’exercice précédent dans le sous-répertoiresrc. -
Compiler les sources en incluant les informations de débogage et de façon à placer les
.classdans le répertoireclasses(cf. documentation dejavac). -
Ajouter un programme principal contenant le code de test de l’exercice précédent et recompiler.
-
Exécuter le programme (cf. documentation de
java). -
Créer une archive Java pour le programme (cf. documentation de
jar) et exécuter à nouveau le programme à partir de l’archive.
5. Module et bibliothèques
5.1. Module en Java
5.1.1. Définition d’un module
-
Pour créer un module ou y ajouter une classe ou une interface, on place une instruction
packageau début du fichier source
package monpackage;
-
Tout ce qui est défini dans le fichier source fait alors parti du module
-
Sans instruction de ce type, les éléments se trouvent dans le module par défaut (non nommé)
-
Les noms des modules respectent en général une convention (par exemple,
uvsq.in404.monpackage) -
La librairie Java est organisée en module (
java.lang,java.util,java.io, \dots)
5.1.2. Interface d’un module
-
Seul les éléments publics sont accessibles à l’extérieur du module
-
Pour rendre une classe ou une interface publique, on spécifie le mot-clé
publicdans sa déclaration
public class MaClasse { //...
5.1.3. Utilisation d’un module
-
Différentes façons d’utiliser les éléments public d’un module
-
utiliser son nom qualifié (par exemple,
uvsq.in404.monpackage.MaClasse) -
importer l’élément (par exemple,
import uvsq.in404.monpackage.MaClasse;placé en début de fichier source) -
importer le module complet (par exemple,
import uvsq.in404.monpackage.*;placé en début de fichier source) -
importer les classes imbriquées (
import uvsq.in404.monpackage.MaClasse.*;) -
importer les membres de classes (
import static uvsq.in404.monpackage.MaClasse.*;)
-
-
Les directives
importse placent avant toute définition de classes ou d’interfaces mais après l’instructionpackage -
Deux modules sont automatiquement importés : le module par défaut et
java.lang
5.1.4. Module et gestion des sources en Java
-
Dans un fichier source
-
plusieurs éléments (classes, interfaces, …) peuvent être définies
-
un seul élément peut être public
-
le nom de l’élément public doit être le même que le nom du fichier
-
-
On se limite de préférence à une classe par fichier source
-
le nom du fichier
.javaest le même que le nom de l’élément qu’il contient
-
-
Le nom du répertoire doit refléter le nom du paquetage
-
la classe
uvsq.in404.monpackage.MaClassedoit se trouver dans le fichierMaClasse.javadu répertoireuvsq/in404/monpackage
-
5.1.5. Module et compilation
-
Lors de la compilation, un fichier
.classest créé pour chaque élément -
La hiérarchie de répertoires contenant les
.classreflète les noms des modules -
Les répertoires où sont recherchées les classes lors de l’exécution sont listés dans le class path
-
Par défaut, le répertoire courant et la librairie Java se trouve dans le class path
-
La façon dont le class path est défini dépend de la plateforme
-
en général, on définit une variable d’environnement
CLASSPATH
-
-
Le class path contient des chemins vers
-
des répertoires contenant une arborescence de
.class -
des fichiers
.jar -
des fichiers
.zip
-
5.2. Bibliothèques en Java
5.2.1. Écosystème Java et bibliothèques
-
L’écosystème Java fournit un nombre important de bibliothèques et d’outils de développement
-
Dans un projet de développement logiciel, le choix des bibliothèques à utiliser est une étape importante
-
fonctionnalités, complexité, support de la communauté, licence, …
-
-
La plupart des programmes Java font appel à des bibliothèques tierces (third party libraries)
5.2.2. Utilisation d’un bibliothèque tierce
-
Récupérer la bibliothèque
-
manuellement (téléchargement)
-
automatiquement (outils de gestion des dépendances comme maven ou gradle)
-
-
Inclure la bibliothèque dans le projet
-
le
CLASSPATHdoit être modifié pour faire référence aux archives (jaren général) de la bibliothèque
-
-
Consulter l’interface de la bibliothèque
-
toute bibliothèque Java est distribuée avec sa documentation au format javadoc
-
-
Importer les modules nécessaires dans les fichiers sources
-
l’utilisation d’une classe de la bibliothèque nécessite d’importer le package Java adéquat
-
5.2.3. Exemple : utilisation de la bibliothèque Apache Commons Math 1/4
-
Télécharger et décompresser le fichier commons-math3-3.6.1-bin.tar.gz
~/commons-math3-3.4.1 $ ls
commons-math3-3.4.1.jar docs/ NOTICE.txt
commons-math3-3.4.1-javadoc.jar LICENSE.txt RELEASE-NOTES.txt
5.2.4. Exemple : utilisation de la bibliothèque Apache Commons Math 2/4
-
Ajouter la bibliothèque au projet (IDE ou outil de build)
5.2.5. Exemple : utilisation de la bibliothèque Apache Commons Math 3/4
-
Importer les classes des packages nécessaires
import org.apache.commons.math3.fraction.Fraction;
public class Main {
public static void main(String[] args) {
Fraction f = new Fraction(1, 3);
System.out.println(f);
}
}
5.2.6. Exemple : utilisation de la bibliothèque Apache Commons Math 4/4
-
Compiler en précisant la bibliothèque dans le CLASSPATH (en ligne de commande)
$ javac -cp ../commons-math3-3.6.1/commons-math3-3.6.1.jar Main.java
-
Éxécuter en précisant la bibliothèque dans le CLASSPATH (en ligne de commande)
$ java -cp ../commons-math3-3.6.1/commons-math3-3.6.1.jar:. Main
5.3. Exercices (Java)
5.3.1. Module et bibliothèque
Dans cet exercice, vous reprendrez le code source de l’exercice simulation de client/serveur.
-
Placez l’ensemble du code source dans le package
in404.exo61. -
Ajoutez une classe contenant le programme principal qui implémentera le scénario de la question 1 de l’exercice.
-
Déplacez la classe
Clientdans le packagein404.exo61.clientet la classeServeurdans le packagein404.exo61.serveur. Apportez les modifications nécessaires au programme principal. -
Construisez une archive
jarcontenant les classes compiléesin404.exo61.client.Clientetin404.exo61.serveur.Serveur. Modifiez le projet pour qu’il utilise cette bibliothèque.
5.3.2. Utilisation d’une bibliothèque tierce
Vous utiliserez ici la bibliothèque Apache Commons Math.
-
En consultant la documentation Javadoc sur le site, identifiez la classe (et son package) permettant la manipulation de nombres complexes.
-
Ajoutez la bibliothèque à votre projet.
-
Écrivez un programme principal pour tester quelques méthodes de la classe.
6. Gestion d’erreurs et exceptions
6.1. Les erreurs en programmation
6.1.1. Les erreurs sont communes
Des erreurs se produisent lors de l’exécution d’un programme
-
Erreurs de syntaxe
-
détectées par le compilateur
-
-
Erreurs d’exécution
-
générées par l’environnement (plus de mémoire disponible, division par zéro, …)
-
-
Erreurs de logique (bogues)
-
liées à une erreur du développeur
-
6.1.2. Plusieurs questions à se poser
-
Que considère-t’on comme une erreur ?
-
Quelle est la gravité de l’erreur ?
-
Comment détecter l’erreur ?
-
Comment propager l’information sur l’erreur ?
-
Comment et où gérer l’erreur ?
-
Comment signaler l’erreur ?
6.1.3. Qu’est-ce qu’une erreur ?
-
Un événement dans une fonction f est une erreur dans l’un des cas suivants :
-
il viole une des préconditions de f,
-
peut être considéré comme une erreur de programmation ⇒ utilisation des assertions
-
-
il empêche f de remplir une des préconditions de ses appelés,
-
il empêche de réaliser une postcondition de f,
-
il empêche f de rétablir un invariant dont elle a la responsabilité.
-
| Les autres événements ne doivent pas être considérés comme des erreurs |
6.2. Gestion d’erreurs
6.2.1. Erreur vs. bogue
-
La gestion d’erreurs est chargée des erreurs d’exécution
-
Les erreurs de logique (bogues) doivent être éliminées durant le développement en utilisant :
-
les assertions
-
le débogage
-
les tests
-
6.2.2. Réaction à une erreur
-
Ignorer le problème
-
en général, c’est une mauvaise idée…
-
-
Retourner un code d’erreur
-
possible si une valeur de retour est disponible pour cela
-
-
Utiliser une variable globale
-
son état doit être consulté
-
-
Lancer une exception
-
propage l’erreur selon la pile d’appel du programme
-
-
Utiliser le type `Option`
-
technique issue de la programmation fonctionnelle
-
6.2.3. Retourner un code d’erreur
public static double sqrtWithReturnCode(double d) {
return Double.isNaN(d) || d < 0.0 ?
Double.NaN :
sqrt(d);
}
double result = sqrtWithReturnCode(value);
if (Double.isNaN(result)) {
System.err.println("Argument illégal (négatif ou égal à NaN).");
} else {
System.out.printf("sqrt(%f) = %f\n", value, result);
}
6.2.4. Utiliser une variable globale
public static enum SqrtError { None, NegArg, NaNArg; }
private static SqrtError sqrtError = SqrtError.None;
public static SqrtError getSqrtError() { return sqrtError; }
public static double sqrtWithGlobalCode(double d) {
if (Double.isNaN(d)) {
sqrtError = SqrtError.NaNArg;
} else if (d < 0) {
sqrtError = SqrtError.NegArg;
}
return sqrt(d);
}
double result = sqrtWithGlobalCode(value);
if (getSqrtError() != SqrtError.None) {
System.err.println("Argument illégal (négatif ou égal à NaN).");
} else {
System.out.printf("sqrt(%f) = %f\n", value, result);
}
6.2.5. Lancer une exception
public static double sqrtWithException(double d) {
if (Double.isNaN(d) || d < 0.0) {
throw new IllegalArgumentException("Argument négatif ou NaN");
}
return sqrt(d);
}
double result;
try {
result = sqrtWithException(value);
System.out.printf("sqrt(%f) = %f\n", value, result);
} catch (IllegalArgumentException ex) {
ex.printStackTrace(System.err);
}
6.2.6. Utiliser le type Option
public static Optional<Double> sqrtWithOption(double d) {
return Double.isNaN(d) || d < 0.0 ?
Optional.empty() :
Optional.of(sqrt(d));
}
Optional<Double> result = sqrtWithOption(value);
System.out.printf("sqrt(%f) = %f\n", value, result.orElse(Double.NaN));
6.3. Exceptions
6.3.1. Définition
-
Le terme exception est un raccourci pour événement exceptionnel
-
Une exception est un événement se produisant lors de l’exécution d’un programme et qui bouleverse le flôt normal d’instructions
-
Le mécanisme des exceptions est destiné à gérer les erreurs ou les cas exceptionnels (une partie du système n’a pas pu réaliser ce qui lui était demandé)
-
"Exceptionnel" ne signifie pas "qui ne se produit presque jamais" ou "désastreux"
-
-
Différents types d’erreurs peuvent générer des exceptions
-
Une exception contient des informations sur l’erreur qui l’a produite
-
Les exceptions peuvent être regroupées et organisées en hiérarchie
6.3.2. Fonctionnement
-
L’action de générer une exception s’appelle lancer (throw) ou lever (raise) une exception
-
Le système d’exécution doit alors trouver une portion de code sachant gérer (handle) ou capturer (catch) cette exception
-
Les candidats pour la gestion de l’exception sont les méthodes de la pile d’appel
-
Le système d’exécution remonte la pile d’appel jusqu’à trouver un gestionnaire d’exception (exception handler) approprié
-
Chaque gestionnaire d’exception gère un type d’exception particulier
-
Le gestionnaire d’exception choisi par le système traite l’exception
-
Si aucun gestionnaire approprié n’est trouvé, le système stoppe le programme
6.3.3. Remarques
-
Une exception peut être redéclenchée
-
un gestionnaire d’exceptions peut faire un traitement partiel puis "passer la main"
-
L’ordre des gestionnaires est important
-
toujours du plus spécialisé au plus général
-
6.3.4. Avantages des exceptions
-
Séparation du code de gestion d’erreurs et du code "normal"
-
évite les "empilements" d’instructions conditionnelles
-
améliore la lisibilité du code
-
-
Propagation des erreurs en suivant la pile d’appels de méthodes
-
simplification de la propagation
-
les méthodes que l’erreur ne concerne pas n’en tiennent pas compte
-
-
Regroupement des types d’erreurs
-
possibilité de gérer ensemble des exceptions de même type
-
par exemple, exceptions concernant un tableau ou un fichier
-
| La gestion d’erreurs reste une tâche difficile ! |
6.3.5. Inconvénients des exceptions
-
Moins structurées qu’une gestion locale
-
revers de la séparation code de gestion d’erreurs/code normal
-
-
Moins efficace
-
le mécanisme de propagation a un coût
-
-
Peut rendre certaines situations complexes
| Le mécanisme des exceptions offre une alternative aux techniques traditionnelles lorsque celles-ci se révèlent insuffisantes, peu élégantes et susceptibles d’introduire des erreurs |
6.4. Exceptions en Java
6.4.1. Généralités
-
Trois catégories d’exceptions
-
une exception non contrôlée (unchecked exceptions) n’est pas destinée à être traitée par le programme
-
une erreur (error) a une cause externe à l’application
-
une exception d’exécution (runtime exception) est provoquée par la JVM
-
-
une exception contrôlée (checked exception) est une exception qui n’est pas lancée par le système d’exécution Java (runtime exception)
-
-
Une exception est une instance d’une classe dérivée de
Throwable -
Une méthode doit soit traiter, soit spécifier toute exception contrôlée qui peut se produire dans cette méthode
-
traiter = fournir un gestionnaire d’exception pour ce type d’exception
-
spécifier = préciser dans sa signature qu’elle peut la lancer
-
-
Le compilateur ne requiert pas que les exceptions du système d’exécution soient traitée ou spécifiée
6.4.2. Le traitement d’une exception comprend
-
Un bloc
try-
autour de la séquence d’instructions susceptible de lancer une exception
-
-
Un ou plusieurs blocs
catch-
représentant les gestionnaires d’exceptions
-
-
Au plus un bloc
finally-
toujours exécuté
-
6.4.3. Le bloc try
-
Le bloc
tryenglobe les instructions susceptibles de lancer une exception
try {
// Instructions
}
-
L’instruction
trygouverne les instructions englobées -
Il définit la portée des gestionnaires d’exceptions qui lui sont associés
-
Une instruction
trydoit être accompagnée d’au moins un bloccatchoufinally
6.4.4. Les blocs catch
-
Les blocs
catchreprésentent les gestionnaires d’exceptions -
Un ou plusieurs blocs
catchsont placés immédiatement après un bloctry
try {
// Instructions
} catch ( /* ... */ ) {
// Instructions
} catch ( /* ... */ ) {
// Instructions
} // ...
-
Les blocs
catchdoivent être ordonnés du plus spécialisé au plus général
6.4.5. Détail d’un bloc catch
-
L’instruction
catchrequiert un unique paramètre
catch (<Type> <variable>) {
// Instructions
}
-
<Type>représente le type de l’exception et doit être une classe dérivant deThrowable -
<variable>est le nom de la variable liée à l’exception (locale au gestionnaire) -
L’argument du
catchressemble à la déclaration d’un paramètre de méthode -
Un gestionnaire peut capturer plusieurs types d’exceptions
-
en capturant une superclasse pour une exception
-
en utilisant plusieurs types dans la clause
catch
-
catch (Type1|Type2|Type3 ex) { //...
6.4.6. Le bloc finally
-
Le bloc
finallyfounit un mécanisme pour "nettoyer" l’état du programme -
Les instructions du bloc
finallysont toujours exécutées -
Le bloc
finallyse place après les gestionnaires d’exceptions du bloctry
finally {
// Instructions
}
6.4.7. Des gestionnaires d’exception pour une pile
try {
Pile unePile = new Pile(2);
unePile.empile("azerty");
unePile.empile("qsdfgh");
unePile.empile("wxcvbn");
assert false : "Jamais atteint";
String str = (String) unePile.depile();
} catch (PileVideException e) {
assert e.getMessage().equals("La Pile est vide");
} catch (PileException e) {
assert e.getMessage().equals("La Pile est pleine");
}
6.4.8. Exception et allocation de ressources
-
La construction try-with-resources gère automatiquement la fermeture des ressources
-
Les classes représentant les ressources doivent implémenter l’interface
AutoCloseable
try (
java.util.zip.ZipFile zf =
new java.util.zip.ZipFile(zipFileName);
java.io.BufferedWriter writer =
java.nio.file.Files.newBufferedWriter(outputFilePath, charset)
) {
// ...
}
6.4.9. Spécification d’exceptions
-
Une spécification d’exception précise qu’une méthode ne capture pas l’exception considérée mais peut la lancer
-
Pour spécifier qu’une ou plusieurs exceptions peuvent être lancées par une méthode, on utilise la clause
throwsdans la signature de la méthode
TypeRetour nomMethode throws Type1Exception, Type2Exception {
//...
}
6.4.10. Spécification d’exceptions pour la pile
empile peut lancer une exception de type PilePleineException/**
* Empile un élément au sommet de la pile.
*
* @param unObjet l'objet à empiler
* @throws PilePleineException s'il n'y a plus de place
*/
public void empile(Object unObjet) throws PilePleineException {
depile peut lancer une exception de type PileVideException/**
* Dépile l'élément se trouvant au sommet de la pile.
*
* @return l'élément au sommet
* @throws PileVideException s'il n'y a pas d'élément
*/
public Object depile() throws PileVideException {
6.4.11. Lancement d’exceptions
-
L’instruction
throwest utilisée pour lancer une exception -
Le mot-clé
throwdoit être suivi d’une instance d’une classe dérivée deThrowable
throw new ClasseDerivéeDeThrowable();
-
Une exception peut être relancée à partir d’un bloc
catch
6.4.12. Lancements d’exceptions pour la pile
/**
* Empile un élément au sommet de la pile.
*
* @param unObjet l'objet à empiler
* @throws PilePleineException s'il n'y a plus de place
*/
public void empile(Object unObjet) throws PilePleineException {
if (sommet == contenu.length) {
throw new PilePleineException();
}
contenu[sommet++] = unObjet;
}
/**
* Dépile l'élément se trouvant au sommet de la pile.
*
* @return l'élément au sommet
* @throws PileVideException s'il n'y a pas d'élément
*/
public Object depile() throws PileVideException {
if (sommet == 0) {
throw new PileVideException();
}
return contenu[--sommet];
}
6.4.13. La classe Throwable
-
La classe
Throwableest la super-classe de toutes les exceptions ou erreurs du langage Java -
Seules les instances de cette classe (ou d’une de ses sous-classe) peuvent être lancées
-
Seule cette classe (ou l’une de ses sous-classe) peut être l’argument d’un
catch -
Contient un instantané de la pile d’exécution au moment de la création de l’instance
-
Peut contenir une cause (une autre instance de
Throwable) afin de gérer une chaîne d’exceptions
6.4.16. Créer des classes exceptions
-
Déterminer dans quelles méthodes et sous quelles conditions des exceptions seront lancées
-
Choisir le type de chaque exception
-
utiliser une exception existante
-
en créer une nouvelle
-
-
Choisir quelle sera la super-classe des exceptions définies
-
Exceptionou l’une de ses sous-classes -
Les exceptions dérivée de
RuntimeExceptionne sont pas contrôlées
-
6.4.18. La classe PileException
package fr.uvsq.info.poo.errors;
/**
* La racine de la hiérarchie d'exception pour la pile.
*
* @author Stéphane Lopes
* @version fév. 2017
*/
class PileException extends Exception {
/**
* Initialise une instance de <code>PileException</code>.
*
* @param message le message d'erreur.
*/
public PileException(String message) {
super(message);
}
}
6.4.19. La classe PileVideException
package fr.uvsq.info.poo.errors;
/**
* Exception pour la pile vide.
*
* @author Stéphane Lopes
* @version fév. 2017
*/
class PileVideException extends PileException {
/**
* Initialise une instance de <code>PileVideException</code>.
*/
public PileVideException() {
super("La Pile est vide");
}
}
6.4.20. La classe PilePleineException
package fr.uvsq.info.poo.errors;
/**
* Exception pour la pile pleine.
*
* @author Stéphane Lopes
* @version fév. 2017
*/
class PilePleineException extends PileException {
/**
* Initialise une instance de <code>PilePleineException</code>.
*/
public PilePleineException() {
super("La Pile est pleine");
}
}
6.5. Exercices
6.5.1. Calculatrice RPN
Dans cette exercice, on souhaite réaliser une calculatrice fonctionnant en mode RPN (Reverse Polish Notation). Cette notation post-fixée permet de représenter des formules arithmétiques sans parenthèses. Par exemple, l’expression \$2 xx (3 + 4)\$ pourra s’écrire \$2 3 4 + xx\$.
Cette calculatrice devra supporter les opérations de base (+, -, *, /) sur des nombres réels.
L’intervalle de nombres supporté sera spécifié par deux constantes
(MAX_VALUE pour le plus grand nombre en valeur absolue,
MIN_VALUE pour le plus petit nombre en valeur absolue).
L’utilisateur saisira au clavier soit un nombre, soit une opération, soit exit pour sortir.
Chaque saisie se terminera par entrée.
Après chaque saisie, le programme affichera l’expression courante.
L’implémentation pourra utiliser une pile de la façon suivante :
-
les opérandes sont empilées lors de leur saisie,
-
les opérations sont effectuées immédiatement en considérant les opérandes se trouvant au sommet de la pile,
-
le résultat d’une opération est empilé.
-
Déterminer les cas d’erreur. En déduire les exceptions nécessaires pour la gestion des erreurs de cette application.
-
Organiser ces exceptions en hiérarchie et choisissez une classe de base dans la librairie Java
-
Implémenter l’énumération
Operationde la façon suivante :-
déclarer l’attribut
symbolereprésentant le symbole de l’opération (+,-, …), -
définir le constructeur prenant en paramètre le symbole de l’opération,
-
déclarer la méthode abstraite
evalretournant le résultat de l’évaluation de l’opération sur deux opérandes, -
définir les constantes
PLUS,MOINS,MULTetDIV, -
pour chacune des constantes, redéfinir la méthode
eval.
-
-
Implémenter la classe
MoteurRPNpossédant les capacités suivantes :-
enregistrer une opérandes,
-
appliquer une opération sur les opérandes,
-
retourner l’ensemble des opérandes stockées.
-
-
Implémenter la classe
SaisieRPNqui gère les interactions avec l’utilisateur et invoque le moteur RPN. La classejava.util.Scannerpermet de gérer les saisies. -
Implémenter l’énumération
CalculatriceRPNqui contiendra le programme principal.
7. Entrées/sorties et persistance
7.1. Entrées/sorties, flux et persistance
7.1.1. Entrée/sortie et langage de programmation
-
La conception et l’implémentation d’une bibliothèque d’entrée/sortie (I/O) est une tâche difficile
-
la source ou la destination des données peut varier (mémoire, fichier, réseau, …)
-
les types définis par l’utilisateur doivent être pris en charge
-
-
Le concept de flux est une proposition pour cela
-
La persistance et la sérialisation sont également nécessaires pour les I/O en POO
7.1.2. Flux
-
Un flux (stream) est un canal reliant une source (ou une destination) à un programme
-
Une source de données (ou une destination) peut être un fichier, la mémoire, le réseau, …
-
Un flux peut être ouvert en lecture et/ou en écriture
| Les données sont lues ou écrites séquentiellement |
7.1.5. Persistance
-
C’est la capacité de sauvegarder l’état des objets, i.e. les données finales de l’application
-
Elle peut être réalisée avec
-
la bibliothèque d’I/O du langage,
-
à l’aide de bibliothèques spécialisées,
-
grâce à un SGBD externe.
-
-
La persistance pose un certain nombre de problèmes
-
sauvegarde de l’état de l’objet
-
gestion des types de données
-
gestion des références
-
7.1.6. Sérialisation
-
La sérialisation est un processus permettant de transformer un objet en flux d’octets
7.2. I/O en Java
7.2.1. Structure et fonctionalités
La bibliothèque standard Java fournit de nombreuses fonctionnalités liées aux E/S
-
la gestion des flux (
java.io) -
les fichiers à accés aléatoires (
java.nio.channels.SeekableByteChannel,RandomAccessFile) -
des fonctionnalités avancées (
java.nio.channels) -
l’interaction avec le système de fichiers (
java.nio.file,File) -
l’entrée et la sortie standard (
PrintStream,java.util.Scanner,Console) -
la gestion des archives (
java.util.zip,java.util.jar) -
les fichiers d’image (
javax.imageio)
7.2.2. Gestion des flux
-
La librairie se divise en deux hiérarchies de classes
-
les flux de caractères (I/O de texte)
-
les flux d’octets (I/O binaire)
-
-
Un flux est automatiquement ouvert lors de sa création
-
La fermeture d’un flux se fait explicitement avec la méthode
close -
La plupart des méthodes peuvent lancer une exception dérivée de
IOException
7.2.3. Flux de caractères
-
Les classes
ReaderetWritersont les super-classes abstraites pour les flux de caractères -
La plate-forme Java manipule des caractères en se basant sur Unicode
-
Les flux de caractères permettent de convertir ce format interne de/vers le format local
7.2.7. Flux d’octets
-
Les classes
InputStreametOutputStreamsont les super-classes abstraites pour les flux d’octets -
Les flux d’octets supportent la lecture et l’écriture d’octets (8 bits)
7.3. Utilisation des flux
7.3.1. Principaux flux par type d’I/O
| Type d’I/O | Flux de catactères | Flux d’octets |
|---|---|---|
Mémoire |
|
|
|
||
Fichier |
|
|
Affichage |
|
|
7.3.2. Principaux flux par fonction
| Type d’I/O | Flux de catactères | Flux d’octets |
|---|---|---|
Avec buffer |
|
|
Conv. de données |
|
|
Sérialisation |
|
|
Conv. oct./car. |
|
|
7.3.3. Flux de fichiers
-
Les classes des flux de fichiers sont
-
FileReader/FileWriterpour l’accès aux fichiers textes -
FileInputStream/FileOutputStreampour les fichiers binaires
-
-
Un flux de fichier peut être créé
-
à partir d’un nom de fichier sous la forme d’une chaîne de caractères
-
d’une instance de
File -
d’une méthode de classe de
java.nio.file.Files(newInputStream, …)
-
7.3.4. Copie d’un fichier texte
/**
* Copie un fichier caractère par caractère.
*/
private static void textFileCopy(String inFilename, String outFilename) throws IOException {
try (
FileReader in = new FileReader(inFilename);
FileWriter out = new FileWriter(outFilename)
) {
int c;
while ((c = in.read()) != END_OF_STREAM) {
out.write(c);
}
}
}
7.3.5. Copie d’un fichier texte (avec buffer)
/**
* Copie un fichier en utilisant un buffer.
*/
private static void bufferedTextFileCopy(String inFilename, String outFilename) throws IOException {
Path inPath = Paths.get(inFilename);
Path outPath = Paths.get(outFilename);
try (
BufferedReader in = Files.newBufferedReader(inPath);
BufferedWriter out = Files.newBufferedWriter(outPath)
) {
String line;
while ((line = in.readLine()) != null) {
out.write(line);
out.newLine();
}
}
}
7.3.6. Copie d’un fichier texte (avec readAllLines)
/**
* Copie un fichier texte.
* Le fichier doit être de taille raisonnable car il est chargé en totalité en mémoire.
*/
private static void simpleTextFileCopy(String inFilename, String outFilename) throws IOException {
Path inPath = Paths.get(inFilename);
Path outPath = Paths.get(outFilename);
List<String> lines = Files.readAllLines(inPath);
Files.write(outPath, lines);
}
7.3.7. Copie d’un fichier binaire
/**
* Copie un fichier octet par octet.
*/
private static void binaryFileCopy(String inFilename, String outFilename) throws IOException {
try (
FileInputStream in = new FileInputStream(inFilename);
FileOutputStream out = new FileOutputStream(outFilename)
) {
int c;
while ((c = in.read()) != END_OF_STREAM) {
out.write(c);
}
}
}
7.3.8. Flux de filtrage
-
Certaines classes de flux sont destinées à appliquer un traitement sur (à filtrer) un flux
-
Les super-classes abstraites pour cela sont
-
FilterReader/FilterWriterpour les caractères -
FilterOutputStream/FilterInputStreampour les octets
-
-
Des flux personnalisés peuvent être définis en héritant de ces classes
7.3.9. Principaux flux de filtrage
-
Les principaux flux de filtrage pour les flux d’octets sont
-
DataInputStreametDataOutputStreampour les I/O des types primitifs -
BufferedInputStreametBufferedOutputStreampour des I/O avec buffer -
PrintStreampour l’affichage des données -
PushbackInputStreampour pouvoir "annuler" la lecture d’une séquence d’octets
-
-
Le seul flux de filtrage pour les flux de caractères est
PushbackReader(permet d'"annuler" la lecture d’une séquence de caractères)
7.3.10. Flux de filtrage et modèle de conception Décorateur
-
Un flux de filtrage est construit à partir d’un autre flux selon le modèle de conception Décorateur
-
Le flux résultant propose des fonctionnalités plus riches que le flux initial
FileInputStream words = new FileInputStream("words.dat");
BufferedInputStream in = new BufferedInputStream(words);
7.3.11. Ecriture des types primitifs
public static void writeDateToFile(String filename, int numberOfItems, double[] prices, int[] units, String[] descs) throws IOException {
try (DataOutputStream out =
new DataOutputStream(
new BufferedOutputStream(
new FileOutputStream(filename)))
) {
for (int i = 0; i < numberOfItems; i++) {
out.writeDouble(prices[i]);
out.writeInt(units[i]);
out.writeUTF(descs[i]);
}
}
}
7.3.12. Lecture des types primitifs
public static void readDateFromFile(String filename, int numberOfItems, double[] prices, int[] units, String[] descs) throws IOException {
try (DataInputStream out =
new DataInputStream(
new BufferedInputStream(
new FileInputStream(filename)))
) {
for (int i = 0; i < numberOfItems; i++) {
prices[i] = out.readDouble();
units[i] = out.readInt();
descs[i] = out.readUTF();
}
}
}
7.3.13. Entrée et sortie standards en Java
La classe System fournit des flux pour :
-
l’entrée standard (attribut
inde typePrintStream) -
la sortie standard (attribut
outde typeInputStream) -
la sortie d’erreurs (attribut
errde typePrintStream)
7.3.14. Flux d’affichage PrintStream (ou PrintWriter)
-
PrintStream format( /* … */)affiche une chaîne selon un format-
PrintStream printf(/* … */)fait la même chose
-
void print(/* … */)affiche différents types de données sur le flux-
void println(/* … */)idem mais suivi d’un retour à la ligne
-
-
PrintStream append(/* … */)ajoute des caractères au flux -
void flush()force la sortie des caractères -
Ne lancent jamais d’exception mais positionnent un indicateur interne
-
interrogeable avec la méthode
boolean checkError()
-
7.3.15. Lire à partir de l’entrée standard
-
L’entrée standard est utilisée en décorant
System.inavecInputStreamReader(voire avecBufferedReader)
InputStreamReader stdin = new InputStreamReader(System.in);
BufferedReader bufferedStdin = new BufferedReader(stdin));
-
La classe
java.util.Scannersimplifie le processus de saisie-
permet de découper un flux en token
-
7.3.16. Accéder à l’entrée et la sortie standard (avec Scanner)
System.out.println("Votre nom et votre âge ? ");
Scanner s = new Scanner(System.in);
String nom = s.next();
int age = s.nextInt();
System.out.format("Votre nom est %s et vous avez %5d ans.%n", nom, age);
7.3.17. Accéder à l’entrée et la sortie standard (avec BufferedReader)
System.out.println("Votre nom et votre age ? ");
BufferedReader in = new BufferedReader(new InputStreamReader(System.in));
String nom = in.readLine();
int age = Integer.parseInt(in.readLine());
System.out.format("Votre nom est %s et vous avez %5d ans.%n", nom, age);
7.3.18. Utiliser la classe Console
-
La classe
Consoleest une alternative pour l’accès à l’entrée et à la sortie standard -
Un objet de ce type est initialisé avec la méthode
System.console()
Console c = System.console();
if (c == null) {
System.err.println("No console.");
System.exit(1);
}
7.4. Sérialisation en Java
7.4.1. Sérialisation
-
La sérialisation est assurée par les classes
ObjectInputStreametObjectOutputStream -
ObjectOutputStreamimplémente les interfacesDataOutputetObjectOutput -
ObjectInputStreamimplémente les interfacesDataInputetObjectInput
7.4.2. Ecrire des objets dans un flux
private static void writeObjectsToFile(Student[] students, String filename) throws IOException {
try (ObjectOutputStream oos =
new ObjectOutputStream(
new FileOutputStream(filename))
) {
oos.writeObject(LocalDate.now());
oos.writeObject(students);
}
}
7.4.3. Lire des objets à partir d’un flux
private static LocalDate readObjectsFromFile(Student[] students, String filename) throws IOException, ClassNotFoundException {
try (ObjectInputStream ois =
new ObjectInputStream(
new FileInputStream(filename))
) {
LocalDate date = (LocalDate) ois.readObject();
students = (Student[]) ois.readObject();
return date;
}
}
7.4.4. Rendre une classe sérialisable
-
Un objet est sérialisable uniquement si sa classe implémente l’interface
Serializable -
L’interface
Serializablene comporte aucune méthode et ne sert qu’à spécifier les classes sérialisables
7.4.5. Une classe sérialisable
package fr.uvsq.info.poo.io;
import java.io.Serializable;
public class Student implements Serializable {
private int number;
private String name;
public Student(int number, String name) {
this.number = number;
this.name = name;
}
@Override
public String toString() {
return String.format("Student{number: %d, name: '%s'}", number, name);
}
}
7.4.6. Gérer la version des classes
-
L’attribut de classe
serialVersionUIDprécise la version d’une classe sérialisable -
Il permet de déterminer si un objet correspond bien à la classe présente dans la JVM
-
Il est généré par la JVM s’il n’est pas défini dans la classe
-
Le développeur peut gérer les versions lui-même
private static final long serialVersionUID = 354054054054L;
7.4.7. Contrôler la sérialisation
-
La sérialisation est gérée par les méthodes
-
defaultWriteObjectdeObjectOutputStream -
defaultReadObjectdeObjectInputStream
-
-
Le comportement par défaut de la sérialisation d’un objet est de stocker
-
la classe de l’objet
-
la signature de la classe
-
la valeur des attributs d’instances y compris les références (mais pas les attributs
transcient)
-
-
Il est possible d’adapter le comportement par défaut en redéfinissant
writeObjectetreadObject -
L’interface
Externalizablepermet d’avoir un contrôle complet du processus de sérialisation
7.5. Exercices
7.5.1. Utilisation des flux de caractères
Le but de cette exercice est de réaliser quelques outils de manipulation de fichiers textes.
-
Écrire un programme prenant en argument un nom de fichier et affichant le nombre de lignes de ce fichier. Quelle classe du package
java.iovous sera utile pour cette tâche ? -
Ècrire un programme prenant en argument une chaîne de caractères et un nom de fichier et qui affiche les lignes (avec leur numéro) du fichier contenant la chaîne. Quelle classe du package
java.iovous sera utile pour cette tâche ?
7.5.2. Implémentation d’un flux de filtrage
Cette exercice consiste à implémenter une commande grep simplifiée.
Parmi les nombreuses options de cette commande, vous implémenterez le sous-ensemble suivant
(cf. page de manuel) :
--help, -e|--regexp, -f|--file, -i|--ignore-case, -n|--line-number.
|
Vous utiliserez la bibliothèque
|
-
Réalisez le flux de filtrage
GrepReaderqui lit un flux en retournant uniquement les lignes correspondant au motif recherché -
Réaliser la classe principale
GrepApp
8. Gestion des collections
8.1. Introduction
8.1.1. Collection
-
Une collection (conteneur) est un objet qui regroupe plusieurs éléments en une seule unité
-
Une collection peut être utilisée pour stocker et manipuler des données et pour transmettre des données d’une méthode à une autre
-
Une collection regroupe généralement des objets de même type
8.1.2. Bibliothèques pour les collections
-
Une bibliothèque pour les collections (collection framework) est une architecture unifiée pour représenter et manipuler des collections
-
Elle différencie trois composants :
-
des interfaces permettent de manipuler des collections indépendamment de leurs implémentations
-
des implémentations représentent les structures de données proprement dites
-
des algorithmes effectuent des traitements par l’intermédiaire des interfaces
-
-
Ce type de bibliothèque s’appuie en général sur la généricité pour le contrôle des types
8.1.3. Quelques exemples de bibliothèques pour les collections
- Java
-
Java Collections Framework, Apache Commons Collections, Google Guava
- C++
-
Standard Containers de la STL (Standard Template Library), Boost
- Python
- C#
- Rust
8.1.4. Motivations
-
\$i xx j\$ versions différentes du même algorithme pour supporter tous les conteneurs
⇒ pour k algorithmes, \$i xx j xx k\$ programmes
-
La généricité permet de paramétrer par rapport aux types
⇒ réduit à \$j xx k\$ programmes
-
Abstraction de la notion de collection (un algo. fonctionne sur toute collection)
⇒ réduit à \$j + k\$ programmes
8.1.5. Intérêt d’une bibliothèque pour les collections 1/2
-
Réduit l’effort de programmation
-
fournit des SDD et des algorithmes permettant de se concentrer sur les parties importantes d’un problème sans s’occuper de détails d’implémentation
-
-
Améliore la qualité et les performances du programme
-
fournit des SDD et des algorithmes de haute qualité et efficaces.
-
il est aussi possible de remplacer simplement une SDD par une autre
-
-
Permet l’interopérabilité d’API
-
Les API peuvent échanger des collections
-
8.1.6. Intérêt d’une bibliothèque pour les collections 2/2
-
Réduit l’effort d’apprentissage et d’utilisation
-
permet de se concentrer sur une seule API
-
-
Réduit l’effort de conception de nouvelles API
-
les nouvelles API s’appuient sur les collections existantes
-
-
Améliore la réutilisabilité du logiciel
-
une nouvelle SDD ou un nouvel algorithme s’intégrant dans la bibliothèque est immédiatement utilisable avec le reste
-
8.1.9. Caractéristiques communes
-
Une collection Java ne peut pas contenir une donnée d’un type primitif (uniquement des objets)
-
Les composants de la bibliothèque de collections se trouvent dans
java.util -
Le découpage interface/implémentation repose sur le modèle de conception Pont
8.2. Interfaces
8.2.1. Les interfaces
-
Les interfaces sont utilisées pour manipuler des collections et les transmettre d’une méthode à une autre
-
Les interfaces permettent de manipuler les collections indépendamment des différentes implémentations
-
représente les types de structures de données
-
il est préférable de manipuler les collections par les interfaces plutôt que par les implémentations
-
-
Une implémentation a la possibilité de ne pas supporter toutes les méthodes de modification de l’interface (lancement de l’exception
UnsupportedOperationException) -
Les implémentations du JDK implémentent toutes les méthodes optionnelles
8.2.2. La hiérarchie de Collection
-
Iterableautorise l’utilisation du foreach -
D’autres interfaces existent mais sont liées à la concurrence
8.2.3. La hiérarchie de Map
-
La hiérarchie de
Mapreprésentent des tableaux associatifs (dictionnaires)
8.2.4. L’interface Collection 1/2
-
L’interface
Collectionest la racine de la hiérarchie de collection -
Le JDK ne fournit pas d’implémentation spécifique pour cette interface
⇒ toutes les implémentations conviennent
-
C’est le plus petit dénominateur commun pour les implémentations
-
Elle doit être utilisée quand un maximum de généralité est souhaitée
8.2.5. L’interface Collection 2/2
public interface Collection<E> extends Iterable<E> {
// Opérations simples
int size();
boolean isEmpty();
boolean contains(Object element);
boolean add(E element); // Optionnel
boolean remove(Object element); // Optionnel
boolean equals(Object o);
int hashCode();
Iterator<E> iterator();
// Opérations de groupe
boolean containsAll(Collection<?> c);
boolean addAll(Collection<? extends E> c); // Optionnel
boolean removeAll(Collection<?> c); // Optionnel
default boolean removeIf(Predicate<? super E> filter);
boolean retainAll(Collection<?> c); // Optionnel
void clear(); // Optionnel
// Conversions
Object[] toArray();
<T> T[] toArray(T[] a);
// Itération et Streams
Iterator<E> iterator();
default Spliterator<E> spliterator();
default Stream<E> stream();
default Stream<E> parallelStream();
}
8.2.6. Parcourir des collections
Trois techniques permettent de parcourir des collections
-
Les Streams
-
La boucle for-each
-
Les itérateurs
8.2.7. Les Streams
String joined = elements.stream()
.filter(e -> e.getColor() == Color.RED)
.map(Object::toString)
.collect(Collectors.joining(", "));
-
Plus de détails dans le chapitre suivant
8.2.8. Boucle for-each
for (String element : uneCollectionDeChaines) {
// Manipuler element
}
-
La boucle for-each ne permet pas de modifier la collection lors de l’itération
⇒ utiliser un itérateur dans ce cas
8.2.9. Itérateur
-
Un itérateur permet de parcourir une collection
-
La notion d’itérateur est implantée en Java par l’interface
Iterator -
Un itérateur peut être vu comme un marqueur se trouvant entre deux éléments
public interface Iterator<E> {
boolean hasNext();
E next();
default void remove() {
throw new UnsupportedOperationException("remove");
}
default void forEachRemaining(Consumer<? super E> action) {
Objects.requireNonNull(action);
while (hasNext())
action.accept(next());
}
}
-
L’utilisation de
removeest le seul moyen sûr de modifier une collection lors du parcours -
On ne peut utiliser
remove()qu’une seule fois par appel ànext() -
Un itérateur est basé sur le modèle de conception Itérateur
8.2.10. Parcours d’une collection avec un itérateur
for (Iterator<String> i = uneCollectionDeChaines.iterator(); i.hasNext(); ) {
String element = i.next(); // Récupère l'élément et passe au suivant
// ...
}
8.2.11. L’interface Set
-
L’interface
Setreprésente une collection sans duplicat-
modélise le concept mathématique d’ensemble
-
-
L’interface
Setn’ajoute aucune méthode à l’interfaceCollection -
Deux ensembles sont égaux s’ils contiennent les mêmes éléments
-
Sémantique des méthodes de groupe
-
s1.containsAll(s2)retournetruesi \$s_2 sube s_1\$ -
s1.addAll(s2)transforme s1 en \$s_1 uu s_2\$ -
s1.retainAll(s2)transforme s1 en \$s_1 nn s_2\$ -
s1.removeAll(s2)transforme s1 en \$s_1 \\ s_2\$
-
8.2.12. L’interface List 1/2
-
L’interface
Listreprésente une séquence d’éléments, i.e. une collection ordonnée -
L’interface
Listpossède des opérations pour:-
accéder aux éléments d’une liste par leurs indices
-
retourner l’indice d’un objet que l’on recherche
-
étendre la sémantique des itérateurs
-
manipuler des sous-listes
-
-
Deux listes sont égales si elles possèdent les mêmes éléments dans le même ordre
8.2.13. L’interface List 2/2
public interface List<E> extends Collection<E> {
// Accès par position
E get(int index);
E set(int index, E element); // Optionnel
void add(int index, E element); // Optionnel
E remove(int index); // Optionnel
boolean addAll(int index, Collection<? extends E> c); // Optionnel
// Opération de groupe
default void replaceAll(UnaryOperator<E> operator) {
Objects.requireNonNull(operator);
final ListIterator<E> li = this.listIterator();
while (li.hasNext()) {
li.set(operator.apply(li.next()));
}
}
default void sort(Comparator<? super E> c) {
Collections.sort(this, c);
}
// Recherche
int indexOf(Object o);
int lastIndexOf(Object o);
// Itération
ListIterator<E> listIterator();
ListIterator<E> listIterator(int index);
// Vue en sous-liste
List<E> subList(int from, int to);
}
8.2.14. Itérateur de liste
-
L’interface
ListIteratorétend l’interfaceIteratorpour permettre un parcours dans les deux sens
public interface ListIterator<E> extends Iterator<E> {
boolean hasPrevious();
E previous();
int nextIndex();
int previousIndex();
void set(E o); // Optionnel
void add(E o); // Optionnel
}
8.2.15. L’interface Queue
-
L’interface
Queuereprésente une file -
Les méthodes sont proposées sous deux formes
| lance une exception | retourne une valeur spéciale | |
|---|---|---|
Insertion |
|
|
Suppression |
|
|
Accès |
|
|
-
La stratégie d’insertion/suppression est définie par l’implémentation
8.2.16. L’interface DeQueue
-
L’interface
DeQueuereprésente une file à double entrée
| Accès en tête | Accès en queue | |||
|---|---|---|---|---|
Exception |
Valeur spéciale |
Exception |
Valeur spéciale |
|
Insertion |
|
|
|
|
Suppression |
|
|
|
|
Accès |
|
|
|
|
8.2.17. L’interface Map 1/2
-
L’interface
Mapreprésente un tableau associatif (dictionnaire)-
i.e. un objet qui associe une clé à chaque valeur
-
-
Une clé peut correspondre à au plus une valeur
-
pas de multi-map en Java ⇒ utiliser une map avec une liste de valeurs associées à chaque clé
-
-
Deux tableaux associatifs sont égaux s’ils représentent les mêmes associations clé/valeur
Les objets mutables comme clés d’une Map peuvent poser problème
|
8.2.18. L’interface Map 2/2
public interface Map<K, V> {
// Opérations de base
int size();
boolean isEmpty();
V put(K key, V value);
V get(Object key);
V remove(Object key);
boolean containsKey(Object key);
boolean containsValue(Object value);
// Opérations sur des groupes
void putAll(Map<? extends K,? extends V> t);
void clear();
// Vues
public Set<K> keySet();
public Collection<V> values();
public Set<Map.Entry<K,V>> entrySet();
// Interface pour entrySet
public interface Entry<K, V> {
K getKey();
V getValue();
V setValue(V value);
}
// + des méthodes par défaut
}
8.2.19. Parcourir une Map
-
Les méthodes de vue comme collection permettent de voir une
Mapcomme une collection de trois façons différenteskeySetreprésente l'ensemble des clés
valuesreprésente la collection des valeurs
entrySetreprésente l’ensemble des couples (clé, valeur)
-
Ces méthodes fournissent un moyen pour parcourir une
Map
8.2.20. Construire l’histogramme d’une image
/**
* Produit l'histogramme d'une image.
*
* @param img l'image à analyser
* @return l'histogramme de l'image sous la forme d'un dictionnaire (couleur, fréquence)
*/
public static Map<Integer, Long> frequency(int[][] img) {
Stream<Integer> imgColors = Arrays.stream(img)
.flatMap(c -> Arrays.stream(c).boxed());
Map<Integer, Long> frequencyMap = imgColors.collect(
Collectors.groupingBy(
Function.identity(), Collectors.counting()
)
);
return frequencyMap;
}
int[][] image = {
{0, 1, 12, 1},
{1, 12, 12, 12},
{0, 12, 12, 0}
};
Map<Integer, Long> histogramme = frequency(image);
System.out.println(histogramme); // {0=3, 1=3, 12=6}
System.out.println(histogramme.keySet()); // [0, 1, 12]
Optional<Long> max = histogramme.values().stream()
.max(Comparator.naturalOrder());
System.out.format("Fréq. max. : %d%n", max.orElse(-1L)); // Fréq. max. : 6
8.2.21. Ordonner des objets
-
Comment ordonner des objets selon leur ordre naturel (lexicographique pour les chaînes, chronologique pour les dates, …) ?
-
La solution proposée est d’implémenter l’interface
Comparable
8.2.22. L’interface Comparable
public interface Comparable<T> {
public int compareTo(<T> o);
}
-
La plupart des classes de la librairie Java implémentent cette interface
-
compareTodoit retourner un entier négatif (respectivement zéro, un entier positif) si l’objet est inférieur (respectivement égal, supérieur) au paramètre -
Si l’argument n’est pas du bon type,
compareTodoit lancer l’exceptionClassCastException -
L’ordre ainsi défini doit induire un ordre partiel (cf. la documentation de
Comparable)
8.2.23. Définir un ordre naturel sur des personnes
class Person implements Comparable<Person> {
/**
* Compare deux personnes.
* La comparaison se fait d'abord selon l'ordre
* lexicographique du nom puis selon le prénom.
*
* @param p une personne
* @return la valeur de comparaison entre les chaînes représentant les noms
* ou entre les prénoms si les noms sont égaux.
*/
@Override
public int compareTo(Person p) {
int cmpNom = nom.compareTo(p.nom);
return (cmpNom != 0 ? cmpNom : prenom.compareTo(p.prenom));
}
8.2.24. Ordonner des objets selon un ordre spécifique
-
Comment ordonner des objets selon un ordre particulier (différent de l’ordre naturel) ?
-
La solution proposée est de fournir un comparateur, i.e. une instance d’une classe implémentant l’interface
Comparator
8.2.25. L’interface Comparator
public interface Comparator<T> {
int compare(T o1, T o2);
}
-
comparedoit retourner un entier négatif (respectivement zéro, un entier positif) si le premier paramètre est inférieur (respectivement égal, supérieur) au second -
Si l’argument n’est pas du bon type,
comparedoit lancer l’exceptionClassCastException -
L’ordre ainsi défini doit induire un ordre partiel (cf. la documentation de
Comparator)
8.2.26. Définir un ordre spécifique sur des personnes
package fr.uvsq.info.poo.collections;
import java.util.Comparator;
/**
* Permet de comparer des personnes selon leur âge.
*
* @author Stéphane Lopes
* @version fév. 2017
*/
class ByAgeComparator implements Comparator<Person> {
/**
* Compare deux personnes selon leur âge.
*
* @return l'écart d'age entre les deux personnes.
*/
public int compare(Person p1, Person p2) {
return p1.getAge() - p2.getAge();
}
}
8.2.27. Trier une liste de personnes
List<Person> lst = Arrays.asList(
new Person("Ariane", "Dupond", 9),
new Person("Cassiopée", "Dupond", 8),
new Person("Hélios", "Martin", 4)
);
Collections.sort(lst); // tri selon l'ordre naturel
System.out.println(lst);
Collections.sort(lst, new ByAgeComparator()); // peut être remplacé par
lst.sort((p1, p2) -> p1.getAge() - p2.getAge()); // peut être remplacé par
lst.sort(Comparator.comparing(Person::getAge)); // tri selon l'âge
System.out.println(lst);
8.2.28. L’interface SortedSet 1/2
-
Cette interface permet de maintenir les éléments d’une ensemble en ordre croissant selon l’ordre naturel ou un ordre spécifié par un comparateur
-
Elle fournit des méthodes pour:
-
manipuler un interval d’éléments
-
accéder au plus petit ou au plus grand élément
-
récupérer le comparateur utilisé (s’il existe)
-
-
Les opérations héritées de
Setfonctionnent à l’identique mais :-
l’itérateur respecte l’ordre
-
toArrayconserve l’ordre
-
8.2.29. L’interface SortedSet 2/2
public interface SortedSet<E> extends Set<E> {
// Vue par intervalle
SortedSet<E> subSet(E fromElement, E toElement);
SortedSet<E> headSet(E toElement);
SortedSet<E> tailSet(E fromElement);
// Extrémités
E first();
E last();
// Comparateur
Comparator<? super E> comparator();
}
8.2.30. L’interface SortedMap 1/2
-
Cette interface permet de maintenir une
Mapordonnée selon l’odre naturel de ses clés ou selon un comparateur -
Elle fournit des méthodes pour:
-
manipuler un interval d’éléments
-
accéder au plus petit ou au plus grand élément
-
récupérer le comparateur utilisé (s’il existe)
-
8.2.31. L’interface SortedMap 2/2
public interface SortedMap<K, V> extends Map<K, V> {
// Range-view
SortedMap<K, V> subMap(K fromKey, K toKey);
SortedMap<K, V> headMap(K toKey);
SortedMap<K, V> tailMap(K fromKey);
// Endpoints
K firstKey();
K lastKey();
// Comparator access
Comparator<? super K> comparator();
}
8.3. Implémentations
8.3.1. Les implémentations
-
Les implémentations sont les structures de données proprement dites
-
On en trouve plusieurs sortes en Java
-
les implémentations généralistes sont les plus couramment utilisées
-
les implémentations spécialisées sont conçues pour un cas particulier
-
les implémentations supportant la concurrence pour les applications multi-threads
-
les implémentations "décorations" permettent de modifier les caractéristiques d’une autre implémentation
-
les implémentations "simples" sont des implémentations minimalistes optimisées pour un cas particulier (singleton par exemple)
-
les implémentations abstraites servent de base pour le développement d’implémentation personnalisée
-
8.3.2. Implémentations généralistes
Interface |
Hashage |
Tableau dyn. |
Arbre équ. |
Liste chaînée |
Hash. + chaîn. |
|
|
|
|
||
|
|
|
|||
|
|
|
|||
|
|
|
|||
|
|
|
|
-
Les implémentations principales sont en gras
-
TreeSet(respectivementTreeMap) implémente égalementSortedSet(respectivementSortedMap) -
Queuepossède aussi pour implémentationPriorityQueue
8.3.3. Caractéristiques des implémentations généralistes
-
Toutes les implémentations implémentent toutes les méthodes optionnelles
-
Toutes sont sérialisables
-
Toutes supportent l’opération
clone -
Elles ne sont pas synchronisées (pour des raisons de performance)
8.3.4. Utilisation des implémentations généralistes
-
Choisir un type d’implémentation
-
Créer une instance de cette implémentation
-
La lier à une référence sur l’interface correspondante
-
le programme reste alors indépendant du choix de l’implémentation
-
Set<Integer> unEnsemble = new HashSet<>();
List<Integer> uneList = new ArrayList<>();
Map<String, String> uneMap = new HashMap<>();
8.3.5. Les implémentations généralistes de Set
-
HashSetest plus rapide mais ne garantit pas l’ordre-
la plupart des opérations sont en temps constant
-
la capacité et le facteur de charge permettent d’affiner les performances de
HashSet
-
-
TreeSetmaintient l’ordre des éléments-
la plupart des opérations sont en temps logarithmique
-
on choisit HashSet sauf si on a besoin d’un ordre sur les éléments
|
8.3.6. Les implémentations généralistes de Map
-
Situation analogue à
Set
on choisit HashMap sauf si on a besoin d’un ordre sur les éléments
|
8.3.7. Les implémentations de List
-
ArrayListest plus rapide-
permet un accès par position en temps constant
-
pas d’allocation à chaque ajout
-
on peut passer une capacité au constructeur de
ArrayList -
possède les opérations
ensureCapacityettrimToSizeen plus de celle de l’interfaceList
-
-
LinkedListest linéaire pour l’accès par position-
adaptée si on fait beaucoup d’insertions/suppressions en milieu de liste (opérations en temps constant mais le facteur constant est élevé)
-
possède les opérations
addFirst,getFirst,removeFirst,addLast,getLast, etremoveLast
-
on choisit ArrayList sauf si on a beaucoup de modifications en milieu de liste
|
8.3.8. Quelques implémentations spécialisées
-
EnumSet(Set) est une implémentation efficace (vecteur de bits) pour les énumérations -
CopyOnWriteArraySet(Set) effectue une copie de l’ensemble pour chaque modification -
CopyOnWriteArrayList(List) effectue une copie de la liste pour chaque modification -
EnumMap(Map) permet d’associer une instance d’énumération à une valeur -
WeakHashMap(Map) autorise la libération de la mémoire d’une paire (clé, valeur) dès que la clé n’est plus référencée
8.3.9. Implémentations "décorations"
-
Une telle implémentation délégue les traitements principaux à une collection particulière mais y ajoute un certain nombre de fonctionnalités
-
modèle de conception Décorateur
-
-
Ces implémentations sont anonymes
-
pas de classe publique mais une méthode de fabrication statique
-
on les obtient par des méthodes de classe (static factory method) de la classe
Collections
-
-
Plusieurs catégories sont disponibles :
-
avec synchronisation pour rendre les collections "tolérantes aux threads"
-
non modifiables pour supprimer toute possibilité de modification d’une collection
-
vérifiant le type dynamiquement
-
List<Integer> list = Collections.synchronizedList(new ArrayList<Integer>());
Collection<String> collec = Collections.unmodifiableCollection(uneCollection);
Set<String> set = Collections.checkedSet(new HashSet<String>(), String.class);
8.3.10. Les implémentations "simples"
-
Ces implémentations sont des "mini" implémentations plus pratiques et généralement plus performantes que les implémentations généralistes
-
Arrays.asListpermet de manipuler un tableau comme une liste -
Collections.nCopiesgénère une liste non modifiable contenant de multiples copies du même élément -
Collections.singletongénère un ensemble non modifiable contenant un unique élément -
emptySet,emptyListetemptyMapde la classeCollectionsreprésentent l’ensemble, la liste et le tableau associatif vides
-
8.3.11. Utiliser des implémentations "simples"
// Créer une liste de taille fixe
List<String> list = Arrays.asList(new String[size]);
// Créer une liste de 1000 éléments initialisés à null
List<Type> l = new ArrayList<>(Collections.nCopies(1000, (Type)null));
// Ajouter 10 fois la chaîne "element" à une collection
uneCollection.addAll(Collections.nCopies(10, "element"));
// Supprimer toutes les occurences de e dans la collection
c.removeAll(Collections.singleton(e));
// Supprimer tous les juristes d'une map
profession.values().removeAll(Collections.singleton(JURISTE));
// Récupérer une liste vide
List<String> s = Collections.emptyList();
8.3.12. Écrire une implémentation
-
La notion d'implémentation abstraite simplifie l’écriture d’une implémentation
-
Une implémentation abstraite est un squelette d’implémentation d’une collection
-
Processus pour écrire son implémentation
-
choisir une implémentation abstraite appropriée
-
implémenter les méthodes abstraites (et éventuellement certaines méthodes concrêtes)
-
tester l’implémentation obtenue
-
si les performances sont importantes, étudier les caractéristiques des méthodes héritées et les redéfinir si nécessaire
-
8.3.13. Principales implémentations abstraites
-
AbstractCollectionpour une collection quelconque-
les méthodes
iteratoretsizedoivent être fournies
-
-
AbstractSetpour un ensemble (même utilisation queAbstractCollection) -
AbstractListpour une liste basée sur une structure à accès aléatoire (comme un tableau)-
les méthodes
get(int)etsizedoivent être fournies
-
-
AbstractSequentialListpour une liste basée sur une structure à accès séquentiel (comme une liste chaînée)-
les méthodes
listIteratoretsizedoivent être fournies
-
-
AbstractQueuenécessite de fournir les méthodesoffer,peek,polletsizeainsi qu’un itérateur supportantremove -
AbstractMappour un tableau associatif-
la vue
entrySetdoit être fournie
-
8.4. Algorithmes
8.4.1. Les algorithmes
-
Les algorithmes sont des méthodes de classe de la classe
Collections -
Le premier paramètre de ces algorithmes est la collection traitée
-
La plupart opèrent sur des listes
8.4.2. Quelques algorithmes disponibles
-
tri :
sort(complexité en \$n log(n)\$, stable) -
mélange :
shuffle -
manipulation des données :
reverse,fill,copy,swap,addAll -
recherche dans une collection triée :
binarySearch -
composition :
frequency,disjoint -
extremum :
min,max
List<Integer> l = new ArrayList<Integer>();
// ...
Collections.sort(l);
int pos = Collections.binarySearch(l, key);
8.5. Mise en œuvre
8.5.1. Exercice sur les listes
-
Créer une liste (basée sur
ArrayList) contenant les 10 premiers entiers -
Mélanger aléatoirement les éléments
-
utiliser la méthode de classe
void shuffle(List<?> list)de la classeCollections
-
-
Afficher la liste à l’endroit puis à l’envers
-
Trier la liste selon l’ordre naturel (croissant) puis selon l’ordre inverse (décroissant)
-
utiliser les méthodes de classe
void sort(List<?> list)etvoid sort(List<?> list, Comparator<? super T> c)de la classeCollections
-
-
Que doit-on changer pour utiliser une
LinkedListà la place d’uneArrayList?
8.5.2. Créer une liste contenant les 10 premiers entiers
List<Integer> uneListe = new ArrayList<>();
for (int i = 0; i < MAX; ++i) uneListe.add(i);
8.5.3. Mélanger aléatoirement les éléments
Collections.shuffle(uneListe);
8.5.4. Afficher la liste à l’endroit puis à l’envers
System.out.println(uneListe);
for (ListIterator<Integer> it = uneListe.listIterator(uneListe.size());
it.hasPrevious(); ) {
System.out.print(it.previous());
System.out.print(", ");
}
8.5.5. Trier la liste selon l’ordre naturel (croissant) puis selon l’ordre inverse (décroissant)
Collections.sort(uneListe);
uneListe.sort((i1, i2) -> i2 - i1);
uneListe.sort(Collections.reverseOrder());
8.5.6. Que doit-on changer pour utiliser une LinkedList à la place d’une ArrayList ?
// Remplacer
List<Integer> uneListe = new ArrayList<>();
// Par
List<Integer> uneListe = new LinkedList<>();
//
// Le reste du programme ne change pas !
//
8.5.7. Exercice sur les tableaux associatifs
-
Créer un tableau associatif (
HashMap) contenant les 10 premiers entiers associés à leur cube -
Afficher les valeurs des cubes triées par ordre croissant
-
Afficher les nombres pairs et leur cube
8.5.8. Créer un tableau associatif contenant les 10 premiers entiers associés à leur cube
Map<Integer, Integer> uneMap = new HashMap<>();
for (int i = 0; i < MAX; ++i) uneMap.put(i, i * i * i);
8.5.9. Afficher les valeurs des cubes triées par ordre croissant
List<Integer> lesCubes = new ArrayList<>(uneMap.values());
Collections.sort(lesCubes);
8.5.10. Afficher les nombres pairs et leur cube
for (Map.Entry<Integer, Integer> elt : uneMap.entrySet()) {
if (elt.getKey() % 2 == 0) {
System.out.println(elt.getKey() + " -> " + elt.getValue());
}
}
8.6. Exercices
8.6.1. Utilisation des collections
L’objet de cet exercice est de simuler l’interrogation d’un DNS (Domain Name Server).
Un DNS convertit une adresse IP (192.168.0.1 par exemple) en un nom qualifié de machine (machine.domaine.local par exemple) et inversement.
Un nom qualifié comporte le nom de la machine (avant le premier '.') et un nom de domaine (après le premier '.').
L’interface proposera une ligne de commande à partir de laquelle les commandes suivantes devront être interprétées:
-
nom.qualifié.machine: l’adresse IP de la machine est affichée; -
adr.es.se.ip: le nom qualifié de cette machine est affiché; -
ls [-a]domaine : la liste des machines du domaine domaine sera affichée triée selon le nom des machines ou selon les adresses IP (si-aest présent).
|
-
Réaliser les classes
AdresseIP,NomMachineetDnsItemqui représentent respectivement une adresse IP, un nom qualifié de machine et une entrée du DNS. -
Réaliser la classe
Dnsqui proposera les opérations suivantes:-
un constructeur qui chargera la base de données,
-
deux méthodes
getItemqui retourneront une instance deDnsItemsoit à partir d’une adresse IP, soit à partir d’un nom de machine, -
une méthode
getItemsqui retournera une liste d’items à partir d’un nom de domaine.
-
-
Réaliser la classe
DnsTUIqui se chargera des interactions avec l’utilisateur. Cette classe fournira une méthodenextCommandequi analysera le texte saisi par l’utilisateur et retournera un objet implémentant l’interfaceCommande(cf. question suivante) et une méthodeaffichequi affichera un résultat. -
Les commandes DNS seront implémentées à l’aide du modèle de conception Commande.
-
créer l’interface
Commandecomportant une seule méthodeexecute, -
créer une classe implémentant cette interface pour chaque action (rechercher une IP, rechercher un nom, rechercher les machines d’un domaine, quitter l’application).
-
-
Réaliser la classe principale
DnsApp. La méthoderunde cette classe interagira avec l’interface utilisateur pour récupérer la prochaine commande, l’exécutera puis affichera la résultat.
8.6.2. Écriture d’algorithmes
Le but de cet exercice est d’écrire des algorithmes pouvant s’appliquer à toute collection respectant l’interface List.
-
Ecrire un algorithme
bubbleSortimplémentant le tri à bulle. Deux versions de l’algorithme seront proposées: la première basée sur l’interfaceComparable, la deuxième sur l’interfaceComparatorpour les comparaisons. -
Ecrire un algorithme
copyIfcréant une copie d’une collection contenant uniquement les éléments vérifiant une condition passée en paramètre
8.6.3. Exercice de synthèse
Le but de cet exercice est de réaliser un logiciel de dessin 2D. On se limitera ici à un affichage textuel, i.e. seule une description des figures sera affichée.
Le logiciel devra offrir les fonctionnalités suivantes:
-
manipulation (affichage et déplacement) des formes comme des rectangles et des cercles,
-
regroupement d’objets afin de leur faire subir un traitement global (par exemple, déplacer ensemble un cercle et un rectangle),
-
sauvegarde/chargement d’un dessin à l’aide de la sérialisation.
|
-
Proposer une hiérarchie de classe modélisant ce problème et un découpage en module du logiciel
-
Réaliser une implémentation du logiciel de dessin
9. La bibliothèques streams et la programmation fonctionnelle en Java
9.1. Introduction
9.1.1. Programmation impérative
-
En programmation impérative, un programme est
-
une séquence d’instructions
-
qui modifie l’état du programme
-
-
Repose sur la séquence d’instructions, l'affectation, les structures de contrôle
-
Permet les effets de bord
-
modifications de zones "partagées"
-
-
Suit le paradigme utilisé au niveau du processeur (langage machine)
-
Basée sur la machine de Turing et l'architecture de von Neumann
-
De nombreux langages supportent ce paradigme
-
C, Java, Python, …
-
9.1.2. Programmation fonctionnelle
-
En programmation fonctionnelle, un programme est
-
un ensemble de fonctions (au sens mathématique) emboîtées
-
sans effets de bord
-
-
Repose sur une approche déclarative, l’évaluation de fonctions
-
Basée sur le \$lambda\$-calcul
-
De plus en plus de langages supportent ce paradigme
-
Haskell, F#, ML, Clojure, Lisp, …
-
Java, Scala, Python, …
-
9.1.3. Fonctions
- Fonction pure
-
fonction sans effet de bord
- Fonction d’ordre supérieur
-
une fonction peut être passée en paramètre ou retournée par une fonctione
- Fonction de première classe
-
les fonctions sont traitées comme les autres données
- Fonction lambda
-
fonction anonyme créée "à la volée"
- Fermeture
-
fonction lambda avec son contexte
9.1.4. Transparence référentielle
-
Remplacer une expression par sa valeur ne change pas le résultat
int globalValue = 2;
int inc(int k) {
globalValue += k;
return globalValue;
}
int result = inc(1) + inc(1)); // result = 3 + 4 = 7
globalValue = 2;
result = 2 * inc(1); // result = 2 * 3 = 6
9.1.5. Avantages
-
Certains programmes s’expriment plus simplement
-
Permet un raisonnement sur le programme (preuve de programmes)
-
Facilite la programmation parallèle et concurrente
-
tire parti des processeurs multi-cœurs
-
9.2. Programmation fonctionnelle en Java
9.2.1. Fonction lambda
-
Une fonction lambda est une fonction anonyme
-
En Java, la syntaxe est composée
-
d’une liste de paramètres formels entre parenthèses
-
d’une flèche →
-
d’une expression ou d’un bloc d’instructions
-
// En précisant le type et avec un bloc
(Person p1, Person p2) -> {
return p1.getAge() - p2.getAge()
}
// Avec une expression (sans return)
(Person p1, Person p2) -> p1.getAge() - p2.getAge()
// Le type des paramètres est optionnel
(person1, person2) -> person1.getAge() - person2.getAge()
// Avec un seul paramètres, les parenthèses sont optionnelles
person -> person.getAge()
9.2.2. Fonction lambda et interface
-
Une fonction lambda peut être utilisée quand une interface fonctionnelle est attendue
-
Une interface fonctionnelle (functional interface) ne doit comporter qu’une unique méthode abstraite
-
L’annotation
@FunctionalInterfacepermet de marquer de telle interface
uneliste.sort(new Comparator<Person> {
public int compare(Person p1, Person p2) {
return p1.getAge() - p2.getAge();
}
});
uneliste.sort((person1, person2) -> person1.getAge() - person2.getAge());
9.2.3. Fermeture
-
Une fermeture est une fonction lamda avec son contexte
public class ClosureDemo {
public static Function<Integer, Integer> ajouteur(int n1) {
return n2 -> n1 + n2;
}
public static void main(String[] args) {
Function<Integer, Integer> ajouteur10 = ajouteur(10);
assert ajouteur10(1) == 11;
}
}
-
En Java, une fermeture ne peut pas modifier les variables de son contexte
9.2.4. Référence de méthode
-
Une référence de méthode permet d’utiliser une méthode comme fonction lambda
-
Quatre types de référence de méthode existent
| Catégorie | Exemple |
|---|---|
Référence à une méthode de classe |
|
Référence à une méthode d’un objet précis |
|
Référence à une méthode d’un objet quelconque |
|
Référence à un constructeur |
|
9.2.5. Référence de méthode (exemples)
List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);
numbers.forEach(e -> System.out.println(e)); // lambda
numbers.forEach(System.out::println); // référence de méthode (objet précis)
numbers.stream()
// .map(e -> String.valueOf(e)) // lambda
.map(String::valueOf) // référence de méthode (méthode de classe)
.forEach(System.out::println);
numbers.stream()
.map(String::valueOf(e))
// .map(e -> e.toString()) // lambda
.map(String::toString) // référence de méthode (objet quelconque)
.forEach(System.out::println);
9.2.6. Parcourir une collection (de l’itératif au fonctionnel) 1/5
List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);
for(int i = 0; i < numbers.size(); i++) {
System.out.println(numbers.get(i));
}
-
Beaucoup de "détails" sont visibles
-
indices limites
-
test d’arrêt
-
accès aux éléments
-
9.2.7. Parcourir une collection (de l’itératif au fonctionnel) 2/5
List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);
for(int e : numbers) {
System.out.println(e);
}
-
Masque les détails mais demeure impératif
9.2.8. Parcourir une collection (de l’itératif au fonctionnel) 3/5
List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);
numbers.forEach(new Consumer<Integer>() {
public void accept(Integer value) {
System.out.println(value);
}
});
-
Syntaxe plus déclarative
-
L’argument de
forEachest ici une classe anonyme -
java.util.function.Consumer<T>est une interface fonctionnelle -
Consumeropère par effet de bord
9.2.9. Parcourir une collection (de l’itératif au fonctionnel) 4/5
List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);
numbers.forEach(value -> System.out.println(value));
-
Beaucoup plus concis et lisible
9.2.10. Parcourir une collection (de l’itératif au fonctionnel) 5/5
List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);
numbers.forEach(System.out::println);
-
Le plus lisible
9.3. La bibliothèque streams
9.3.1. Streams
-
Un flux (stream) est une séquence d’éléments
-
Il véhicule des éléments à partir d’une source à travers un pipeline
-
Un flux ne stocke aucune donnée
-
ce n’est pas une structure de données
-
9.3.2. Pipeline
-
Un pipeline est une séquence d’opérations applicables sur un flux
-
Il comporte
-
une source (collection, tableau, fonction génératrice, flux I/O)
-
une séquence d’opérations intermédiaires (chacune produit un nouveau stream)
-
une opération terminal qui calcule un résultat
-
-
Une opération ne modifie pas le flux d’origine
-
L’évaluation est paresseuse
-
Peut être exécuté séquentiellement ou en parallèle
9.3.3. Opération terminale
-
Une opération terminal traverse le flux pour produire un résultat ou un effet de bord
-
Après exécution, le flux est considéré comme consommé et ne peut pas être réutilisé
-
Une opération terminale est également nommée réduction
9.3.4. Un exemple de stream
List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);
// Calcul le total des doubles des nombres pairs
System.out.println(
numbers.stream()
.filter(e -> e % 2 == 0)
.mapToInt(e -> e * 2)
.sum());
9.3.5. L’interface Stream
-
L’interface
java.util.stream.Streamregroupe l’ensemble des opérations applicables aux pipelines -
Les interfaces
IntStream,LongStreametDoubleStreamsont spécialisés pour les types primitifs
9.3.6. Création d’un flux
-
À partir d’une collection
-
Collection.stream,Collection.parallelStream(flux parallèle)
-
-
À partir d’un tableau (
Arrays.stream) -
À partir d’un intervalle
-
IntStream.range,IntStream.rangeClosed(aussi avecLongStream)
-
-
À partir de valeurs
-
Stream.of(aussi dansIntStream,LongStreametDoubleStream)
-
-
À partir des méthodes de classe de
Stream-
concat,empty,generate/iterate(flux infini)
-
-
À partir de nombres aléatoires (
doubles,intsetlongsde la classeRandom) -
À partir d’un fichier (
Files.lines,BufferedReader.lines)
9.3.7. Quelques opérations intermédiaires
| Opération | Description |
|---|---|
|
retourne les éléments respectant un prédicat |
|
applique une fonction à chaque élément |
|
désimbrique des flux |
|
tronque un flux |
|
ignore les premiers éléments |
|
élimine les doublons (avec état) |
|
retourne un flux trié (avec état) |
9.3.8. Opérations intermédiaires (exemples)
List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);
numbers.stream()
.filter(e -> e % 2 == 0)
.forEach(System.out::println)); // 2 4 6 8 10
numbers.stream()
.filter(e -> e % 2 == 0)
.map(e -> e * 2)
.forEach(System.out::println)); // 4 8 12 16 20
9.3.9. Quelques opérations terminales
| Opération | Description |
|---|---|
|
applique une réduction avec une fonction d’accumulation |
|
compte les éléments |
|
réduction spécialisée sur les flux de types primitifs |
|
réalise une réduction par modification |
|
teste si tous les éléments respectent un prédicat |
|
exécute une action pour chaque élément |
9.3.10. Opérations terminales (exemple) 1/3
reduceList<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);
System.out.println(
numbers.stream()
.filter(e -> e % 2 == 0)
.map(e -> e * 2.0)
.reduce(0.0, (carry, e) -> carry + e));
sum et DoubleStreamSystem.out.println(
numbers.stream()
.filter(e -> e % 2 == 0)
.mapToDouble(e -> e * 2.0)
.sum());
9.3.11. Opérations terminales (exemple) 2/3
collectList<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5, 1, 2, 3, 4, 5);
List<Integer> doubleOfEven =
numbers.stream()
.filter(e -> e % 2 == 0)
.map(e -> e * 2)
.collect(Collectors.toList());
System.out.println(doubleOfEven);
collectMap<Integer, Integer> doubleOfEven =
numbers.stream()
.filter(e -> e % 2 == 0)
.collect(Collectors.toMap(
Function.identity(),
i -> i * 2));
System.out.println(doubleOfEven);
9.3.12. Opérations terminales (exemple) 3/3
List<Person> persons = //...
Map<Person, List<Person>> personsByName =
persons.stream()
.collect(Collectors.groupingBy(Person::getName));
System.out.println(personsByName);
Map<Person.Gender, List<String>> namesByGender =
persons.stream()
.collect(Collectors.groupingBy(
Person::getGender,
Collectors.mapping(
Person::getName,
Collectors.toList())));
9.3.13. Flux infinis
-
Les méthodes de classe
Stream.generateetStream.iteratecréent un flux infini -
Ce type de flux peut exister grâce à l'évaluation paresseuse
-
l’application d’opérations élémentaires ne provoque pas la traversée du pipeline
-
seules les opérations terminales déclenchent le traitement
-
Stream<Integer> integers = Stream.iterate(0, i -> i + 1);
integers.limit(10)
.forEach(System.out::println);
| Il ne faut jamais réduire l’intégralité d’un flux infini. |
9.4. Exercices
9.4.1. Requêtes en utilisant les streams
Le but de cette exercice est d’exprimer un ensemble de requêtes en utilisant la bibliothèque stream.
Un employé possède les attributs suivants :
nom (String), age (int), sexe (enum), salaire (BigDecimal),
date d’embauche (LocalDate) et service de rattachement (Service).
Un service comporte un nom (String) et une adresse (String).
-
Implémentez la classe
Employeet l’énumérationService -
Créez un jeu de données de test
-
Implémentez les requêtes suivantes (faites afficher le résultat)
-
les employés (avec toutes leurs caractéristiques)
-
les employés de moins de 30 ans
-
le nom des hommes
-
le nom et le salaire trié par salaire décroissant
-
la moyenne des salaires
-
les employés regroupés selon leur sexe
-
la moyenne des salaires par sexe
-
le nom et la date d’embauche par services
-
10. Conclusion
10.1. Bilan
10.1.1. Principaux concepts abordés
-
Objet et messages
-
Type et classe, métaclasse, généricité
-
Sous-type, héritage, polymorphisme, classe abstraite, héritage multiple et à répétition
-
Relations entre classes (dépendance, association/agrégation/composition, héritage)
-
Modules
10.1.2. Autres notions abordées
-
Gestion d’erreurs et exceptions
-
Entrées/sorties et persistance
-
Gestion des collections
-
Bibliothèques
streamet notion de programmation fonctionnelle
10.2. Conception orientée-objet
10.2.1. Qu’est-ce que la conception orientée-objet
-
Lors de son exécution, un système OO est un ensemble d’objets qui interagissent
-
La conception orientée-objet (COO) consiste donc à créer un modèle qui respecte les concepts objet
10.2.2. Difficultés de la conception orientée-objet
-
Les concepts objets sont nombreux et complexes (attribut, méthode, objet, classe, héritage, …)
-
⇒ plusieurs solutions sont en général envisageables
-
-
Identifier la bonne solution est difficile
-
Plusieurs symptômes voire métriques de qualité peuvent guider les choix
|
10.2.3. Principes, patterns et idiomes
-
Un principe donne des directives générales
-
souvent indépendant des paradigmes et des langages
-
par exemple KISS, YAGNI, DRY, Law of Demeter/Tell, don’t ask, …
-
-
Un pattern propose une solution reconnue à un problème récurrent
-
en général indépendant des langages
-
par exemple le design pattern Composite
-
-
Un idiome est une construction spécifique d’un langage pour implémenter une situation courante
-
uneListe.forEach(System.out::println);(afficher une liste en Java 8)
-
10.2.4. KISS (Keep It Simple, Stupid)
-
"Simplicity should be a key goal in design and unnecessary complexity should be avoided", Kelly Johnson, ingénieur chez Lockheed
-
Le code le plus simple est aussi le plus simple à maintenir
10.2.5. YAGNI (You Aren’t Gonna Need It)
-
"Do the Simplest Thing That Could Possibly Work"
-
Ne pas ajouter une fonctionnalité avant que cela soit nécessaire
-
Principe issu d’eXtreme Programming
-
Nécessite de s’appuyer sur du refactoring pour être efficace
10.2.6. DRY (Don’t Repeat Yourself)
-
"Every piece of knowledge must have a single, unambiguous, authoritative representation within a system", The Pragmatic Programmer, Andrew Hunt and David Thomas
-
Chaque fonctionnalité doit être réalisée à un seul endroit du code
-
Une modification d’un élément ne nécessite pas de changer un autre élément non relié logiquement
10.2.7. Law of Demeter
|
Law of Demeter or principle of least knowledge
|
-
Une méthode d’un objet devrait invoquer uniquement les méthodes de
-
l’objet lui-même,
-
ses paramètres,
-
tout objet qu’elle instancie,
-
ses composants directs.
-
-
Un objet connaît le minimum de la structure de ses voisins
-
Cela limite les dépendances avec les autres objets
10.2.8. Exemple de la Loi de Demeter 1/5
Wallet theWallet = theCustomer.getWallet();
double totalMoney = theWallet.getTotalMoney();
if (totalMoney > AMOUNT_TO_PAY_IN_EUROS) {
theWallet.subtractMoney(AMOUNT_TO_PAY_IN_EUROS);
}
-
Cet exemple est extrait de The Paperboy, The Wallet, and The Law Of Demeter, David Bock
-
Le créancier connaît la structure du client et manipule lui-même le portefeuille
-
Il dispose de plus d’informations que nécessaire
-
La validité du portefeuille n’est pas garantie
10.2.9. Exemple de la Loi de Demeter 2/5
Customer (version incorrecte)public class Customer {
private String name;
private Wallet wallet;
public Customer(String name, double fortune) {
this.name = name;
wallet = new Wallet(fortune);
}
public Wallet getWallet() {
return wallet;
}
}
10.2.10. Exemple de la Loi de Demeter 3/5
Wallet (version incorrecte)public class Wallet {
private double totalMoney;
public Wallet(double fortune) {
totalMoney = fortune;
}
public double getTotalMoney() {
return totalMoney;
}
public void subtractMoney(double amountToPayInEuros) {
totalMoney -= amountToPayInEuros;
}
}
10.2.11. Exemple de la Loi de Demeter 4/5
double paidAmount = theCustomer.getPayment(AMOUNT_TO_PAY_IN_EUROS);
-
Le créancier doit demander le paiement
-
La classe
Walletest isolée (diminue le couplage) -
La méthode
getPaymentencapsule la logique du paiement (améliore la cohésion) -
La classe
Customerest plus complexe-
mais la complexité a été transférée depuis le code de l’application
-
10.2.12. Exemple de la Loi de Demeter 5/5
Customer (version corrigée)public class Customer {
private String name;
private Wallet wallet;
public Customer(String name, double fortune) {
this.name = name;
wallet = new Wallet(fortune);
}
public double getPayment(double amountToPayInEuros) {
double totalMoney = wallet.getTotalMoney();
double paidAmount = 0.0;
if (totalMoney > amountToPayInEuros) {
paidAmount = amountToPayInEuros;
wallet.subtractMoney(paidAmount);
}
return paidAmount;
}
}
-
La classe
Customerne publie plus la méthodegetWallet()
10.3. Pour aller plus loin…
10.3.1. Lisez !
-
Des livres (cf. la bibliographie en début de cours)
-
Des blogs (DZone, InfoQ, Java Annotated Monthly), wiki (C2 wiki), flux, …
-
Des questions/réponses (Stack Overflow)
-
Du code (Tips For Reading Code, github, Programs To Read)
| pour les langages, attention aux versions (Java >= 8) |
| Ayez l’esprit critique (toutes les pages ne se valent pas) |
10.3.2. Pratiquez !
-
Kata, Koans, Coding Dojo
|
Ajoutez des contraintes
|