You are on page 1of 485

Compléments

en POO

Aldric Degorre
Compléments en POO
Introduction

Style

Objets et
classes Aldric Degorre
Types et
polymorphisme

Héritage Version 2018.11.00 du 22 novembre 2018


Généricité

Gestion des
erreurs et
exceptions Chargés de TP en 2018 :
Interfaces Benjamin Bergougnoux 1 , Isabelle Fagnot 2 , Yan Jurski 3 et Aldric Degorre 4 .
graphiques

Concurrence Ont aussi participé à l’élaboration de ce cours :


Victor Marsault (2015), Cyrille Chenavier (2016), Valia Mitsou (2017)

1. benjamin.bergougnoux@gmail.fr
2. fagnot@irif.fr
3. jurski@irif.fr
4. adegorre@irif.fr
Compléments
en POO La programmation orientée objet
Aldric Degorre Historique

Introduction
Programmation
orientée objet
Java
Guide

Style • Paradigme de programmation inventé dans les années 1960 par Ole-Johan Dahl et
Objets et
classes Kristen Nygaard (Simula : langage pour simulation physique)...
Types et
polymorphisme
• ... complété par Alan Kay dans les années 70 (Smalltalk), qui a inventé l’expression
Héritage “object oriented programming”.
Généricité • Premiers langages OO “historiques” : Simula 67 (1967 5 ), Smalltalk (1980 6 ).
Gestion des
erreurs et
exceptions
• Autres LOO connus : C++, Objective-C, Eiffel, Java, Javascript, C#, Python, Ruby...
Interfaces
graphiques

Concurrence

5. Simula est plus ancien (1962), mais il a intégré la notion de classe en 1967.
6. Première version publique de Smalltalk, mais le développement à commencé en 1971.
Compléments
en POO La programmation orientée objet
Aldric Degorre Principes

Introduction

• Principe de la POO : des messages 7 s’échangent entre des objets qui les traitent
Programmation
orientée objet
Java
Guide
pour faire progresser le programme.
Style

Objets et
• → POO = paradigme centré sur la description de la communication entre objets.
classes
• Pour faire communiquer un objet a avec un objet b, il est nécessaire et suffisant de
Types et
polymorphisme connaître les messages que b accepte : l’interface de b.
Héritage

Généricité
• Ainsi objets de même interface interchangeables → polymorphisme.
Gestion des
erreurs et • Fonctionnement interne d’un objet 8 caché au monde extérieur → encapsulation.
exceptions

Interfaces
graphiques Pour résumer : la POO permet de raisonner sur des abstractions des composants réutili-
Concurrence
sés, en ignorant leurs détails d’implémentation.

7. appels de méthodes
8. Notamment son état, représenté par des attributs.
Compléments
en POO La programmation orientée objet
Aldric Degorre Avantages et raisons du succès de la POO

Introduction
Programmation
orientée objet
Java

La POO permet de découper un programme en composants


Guide

Style

Objets et
classes
• peu dépendants les uns des autres (faible couplage)
Types et → code robuste et évolutif
polymorphisme (composants testables et déboguables indépendamment et aisément remplaçables) ;
Héritage
• réutilisables, au sein du même programme, mais aussi dans d’autres ;
Généricité

Gestion des
→ facilite la création de logiciels de grande taille.
erreurs et
exceptions
POO → façon de penser naturelle pour le cerveau humain, sans connaissance
mathématiques avancées 9 .
Interfaces
graphiques

Concurrence

9. Au contraire de la programmation fonctionelle, avec la théorie des catégories.


Compléments
en POO Java
Aldric Degorre Historique

Introduction
Programmation
orientée objet • 1992 : langage Oak chez Sun Microsystems 10 , dont le nom deviendra Java ;
Java
Guide
• 1996 : JDK 1.0 (première version de Java) ;
Style

Objets et • 2009 : rachat de Sun par la société Oracle ;


classes

Types et • 2014 : Java SE 8, la version long terme actuelle ;


polymorphisme

Héritage
• mars 2018 : Java SE 10, la version actuelle ;
Généricité • 25 septembre 2018 : Java SE 11, prochaine version long terme. 11
Gestion des
erreurs et
exceptions Java est “dans la force de l’âge” : 22 ans (C : 40, Fortran : 64... mais Go : 9, Swift : 4).
Interfaces
graphiques Depuis plusieurs années, Java est le 1er ou 2ème langage le plus populaire. 12
Concurrence
10. Plus précisément, auteurs principaux : James Gosling et Patrick Naughton.
11. Dorénavant : depuis Java 9 une version sort tous les 6 mois, et une version long terme tous les 3 ans.
12. Dans les principaux classements : TIOBE, RedMonk, PYPL, ...
Selon les métriques, l’autre langage le plus populaire est C, Javascript ou Python.
Compléments
en POO Java
Aldric Degorre Caractérisitiques principales

Introduction
Programmation Java est en réalité une 13 plateforme de programmation caractérisée par :
orientée objet

• le langage de programmation Java


Java
Guide

Style • orienté objet à classes,


Objets et
classes
• à la syntaxe inspirée de celle du langage C 14 ,
• au typage statique,
Types et
polymorphisme • à gestion automatique de la mémoire, via son ramasse-miettes (garbage collector).
Héritage

Généricité • son aspect multi-plateforme, via la JVM 15 : le code source Java se compile en une
Gestion des forme (code-octet) exécutable sur de nombreux types de machines physiques.
erreurs et
exceptions
• les bibliothèques officielles du JDK (fournissant l’API 16 Java), très nombreuses et
Interfaces
graphiques bien documentées (+ nombreuses bibliothèques de tierces parties).
Concurrence
13. En fait, plusieurs : notamment Java SE (Standard Edition), la plus courante, et Jakarta EE (ex-Java EE).
14. C sans pointeurs et struct ≃ Java sans objet
15. Java Virtual Machine
16. Application Programming Interface
Compléments
en POO Java
Aldric Degorre Forces

Introduction
Programmation

Domaines d’utilisation :
orientée objet
Java
Guide

Style • applications de grande taille 17 ;


Objets et
classes • partie serveur des applications web/internet (technologies servlet et JSP) 18 ;
Types et
polymorphisme • applications “desktop” 19 (via Swing et JavaFX, notamment) multiplateformes
Héritage (grâce à l’exécution dans une machine virtuelle) ;
Généricité

Gestion des
• applications mobiles (années 2000 : J2ME/MIDP ; années 2010 : Android) ;
erreurs et
exceptions • cartes à puces (via spécification Java Card).
Interfaces
graphiques

Concurrence
17. Facilité à diviser un projet en petits modules, grâce à l’approche OO. Pour les applications de petite
taille, la complexité de Java est, en revanche, souvent rebutante.
18. En concurrence avec PHP, Ruby, Javascript (Node.js) et, plus récemment, Go (dans le cloud).
19. Appelées aujourd’hui “clients lourds”, par opposition à ce qui tourne dans un navigateur web.
Compléments
en POO Java
Aldric Degorre Faiblesses

Introduction
Programmation
orientée objet Mais :
Java
Guide
• Java ne s’illustre plus pour les clients “légers” (dans le navigateur web) : les applets
Style

Objets et
Java ont été éclipsées 20 par Flash 21 puisJavascript.
classes

Types et
• Java n’est pas adapté à la programmation système 22 .
polymorphisme
→ C, C++ et Rust plus adaptés.
Héritage

Généricité • Idem pour le temps réel 23 .


Gestion des
erreurs et
• Idem pour l’embarqué 24 (quoique... cf. Java Card).
exceptions

Interfaces
graphiques 20. Le plugin Java pour le navigateur a même été supprimé dans Java 10.
Concurrence
21. ... technologie aussi en voie de disparition
22. APIs trop haut niveau, trop loin des spécificités de chaque plateforme matérielle.
Pas de gestion explicite de la mémoire.
23. Ramasse-miette qui rend impossible de donner des garanties temps réel. Abstractions coûteuses.
24. Grosse empreinte mémoire (JVM) + défauts précédents.
Compléments
en POO À propos de ce cours
Aldric Degorre Objectifs, contexte

Introduction
Programmation
orientée objet
Java
Guide
Objectif : explorer les concepts de la POO au travers de l’étude du langage Java.
Style
Contexte :
Objets et
classes
• Ce cours fait suite au cours de POO-IG de L2. Principales différences :
Types et
polymorphisme • tout ne sera pas révisé ;
Héritage • certains thèmes approfondis (notamment programmation concurrente), d’autres
Généricité ajoutés (programmation fonctionnelle) ;
Gestion des • surtout, nous insisterons sur la POO (objectifs, principes, techniques, stratégies,
erreurs et
exceptions patrons, bonnes pratiques ...).
Interfaces
graphiques • Il précède les cours LOA de M1 (langage C++) et POCA de M2 (langage Scala) 25 .
Concurrence

25. Et dans une moindre mesure “Théorie et pratique de la concurrence” M1 : un chapitre parle de la
concurrence en Java.
Compléments
en POO À propos de ce cours
Aldric Degorre Pourquoi Java ?

Introduction
Programmation
orientée objet
Java
Guide

Style
Pourquoi un autre “cours de Java” ?
Objets et
classes • Java convient tout à fait pour illustrer les concepts OO. 26
Types et
polymorphisme
• (n + 1)ième contact avec Java → économie du ré-apprentissage d’une syntaxe → il
Héritage reste du temps pour parler de POO.
Généricité
• C’est aussi l’occasion de perfectionner la maîtrise du langage Java (habituellement,
Gestion des
erreurs et il faut plusieurs “couches” !)
exceptions

Interfaces (• Et faut-il encore rappeler à quel point Java est pertinent dans le monde “réel” ?)
graphiques

Concurrence

26. On pourra disserter sur le côté LOO “impur” de Java... mais Java fait l’affaire !
Compléments
en POO À propos de ce cours
Aldric Degorre Quel Java ?

Introduction
Programmation
orientée objet
Java
Guide

Style

Objets et
classes

Types et
Ce cours utilise Java 8.
polymorphisme

Héritage
Java 9, 10 et 11 ne sont pas censurés ; quelques nouveautés en seront présentées.
Généricité D’autres langages illustrent aussi ce cours (C, C++, OCaml, Scala, Kotlin, ... ).
Gestion des
erreurs et
exceptions

Interfaces
graphiques

Concurrence
Compléments
en POO Organisation du cours
Aldric Degorre

Introduction • Volume horaire :


Programmation
orientée objet
Java
• 2h de CM toutes les 2 semaines,
Guide
• 2h de TP chaque semaine.
Style

Objets et • En ligne : 1 cours sur Moodle pour télécharger tous les documents (ce cours, les
classes
fiches de TP, ...) et envoyer vos rendus de projet.
Types et
polymorphisme → code CPOO5-2018
Héritage Inscrivez-vous vite !
Généricité
• Modalités de contrôle : 27
Gestion des
erreurs et • En première session :
exceptions

Interfaces
Des QCM en amphi (2 minimum) → 30% de la note,
graphiques Un projet de programmation → 40% de la note,
Concurrence Un contrôle final écrit → 30% de la note.
• En session de rattrapage : un unique examen (100%).

27. Ça peut encore changer dans les premières semaines, mais si c’est le cas, vous serez avertis.
Compléments
en POO À propos de ces transparents
Aldric Degorre

Introduction • À la fois de support de présentation et support de révision.


Programmation

→ pages assez denses.


orientée objet
Java
Guide

Style
• Ce document va évoluer
Objets et • Chaque semaine : ajout du cours de la semaine (voire de la semaine d’après).
classes
• N’importe quand : corrections d’erreurs, ajouts de précisions, d’explications,
Types et
polymorphisme d’exemples (selon besoins, n’hésitez pas à demander !)
Héritage
• Ainsi, versions numérotées avec numéros de la forme : aaaa.ss.rr :
Généricité
• aaaa est l’année (2018, pour vous),
Gestion des
erreurs et • ss est le numéro de semaine du dernier cours ajouté (nombres impairs de 1 à 11)
exceptions
• et rr est le numéro de révision (0 pour la première version publique du cours de la
Interfaces
graphiques semaine, puis 1 à la première correction, etc.).
Concurrence
Exemple : la version de ce cours est “2018.11.00”.
• Pour faciliter la (re-)lecture, un code de couleurs est mis en place...
Détails
Compléments
en POO Page “normale”
Aldric Degorre

Introduction
Programmation
orientée objet
Java
Guide

Style
Les pages de cette couleur sont les pages “normales” (ce qui ne veut rien dire mais...). Il
Objets et
classes peut s’agir :
Types et
polymorphisme • de celles qui ne tombent dans aucune des autres catégories
Héritage
• de celles qui devraient appartenir à plusieurs catégories
Généricité

Gestion des • et enfin de celles qui n’ont pas encore été catégorisées (oubli, hésitation...)
erreurs et
exceptions

Interfaces
graphiques

Concurrence
Compléments
en POO Révision
Aldric Degorre

Introduction
Programmation
orientée objet
Java
Guide

Style

Objets et
classes Les pages de cette couleur sont celles dont le contenu constituent une révision du cours
Types et
polymorphisme POO-IG de L2.
Héritage
Vérifiez que vous maîtrisez bien les notions qui y sont abordées.
Généricité

Gestion des
erreurs et
exceptions

Interfaces
graphiques

Concurrence

Révision
Compléments
en POO Exemple/illustration
Aldric Degorre

Introduction
Programmation
orientée objet
Java
Guide

Style

Objets et
classes
Les pages de cette couleur sont celles qui contiennent un exemple ou une illustration et
Types et
polymorphisme un éventuel commentaire.
Héritage
Dès lors qu’on généralise sur l’exemple, ce sera une diapo “normale”.
Généricité

Gestion des
erreurs et
exceptions

Interfaces
graphiques

Concurrence

Exemple
Compléments
en POO Détail/développement
Aldric Degorre

Introduction
Programmation
orientée objet
Java
Guide

Style

Objets et Les pages de cette couleur contiennent des détails techniques qui apportent peu à la
classes
compréhension profonde des notions en cours de présentation... mais sont
Types et
polymorphisme indispensables pour les utiliser concrètement.
Héritage
Probablement ces pages ne seront que très rapidement montrées lors du cours
Généricité

Gestion des
magistral, mais il sera indispensable de leur consacrer du temps en seconde lecture.
erreurs et
exceptions

Interfaces
graphiques

Concurrence

Détails
Compléments
en POO Réflexion préliminaire/analyse
Aldric Degorre

Introduction
Programmation
orientée objet
Java
Guide

Style

Objets et
classes
Les pages de cette couleur correspondent à des discussions servant à amener la notion
Types et
qui est sur le point d’être abordée.
polymorphisme

Héritage
On y décrit un problème et ses enjeux... mais pas encore la solution apportée par Java.
Généricité Le contenu est un raisonnement qu’il faut comprendre, mais pas apprendre par cœur.
Gestion des
erreurs et
exceptions

Interfaces
graphiques

Concurrence

Analyse
Compléments
en POO Synthèse/résumé/à retenir
Aldric Degorre

Introduction
Programmation
orientée objet
Java
Guide

Style

Objets et
classes

Types et Les pages de cette couleur contiennent une synthèse des dernières pages, c’est-à-dire
polymorphisme
soit un résumé, soit le résultat principal à retenir.
Héritage

Généricité

Gestion des
erreurs et
exceptions

Interfaces
graphiques

Concurrence

Résumé
Compléments
en POO Discussion/ouverture
Aldric Degorre

Introduction
Programmation
orientée objet
Java
Guide

Style

Objets et
classes Les pages de cette couleur contiennent une discussion, un commentaire, des remarques,
Types et ou bien une ouverture relative à ce qui vient d’être abordé.
polymorphisme

Héritage Ces pages doivent servir à faire réfléchir, mais ne sont en aucun cas à apprendre par
Généricité cœur.
Gestion des
erreurs et
exceptions

Interfaces
graphiques

Concurrence

Discussion
Compléments
en POO Approfondissement
Aldric Degorre

Introduction
Programmation
orientée objet
Java
Guide

Style

Objets et
classes
Les pages de cette couleur contiennent un approfondissement du cours. Cela veut dire
Types et
polymorphisme que le contenu est intéressant à apprendre, dès lors qu’on comprend le reste du cours,
Héritage mais n’est pas prioritaire (sur les pages “normales”).
Généricité

Gestion des
erreurs et
exceptions

Interfaces
graphiques

Concurrence

Supplément
Compléments
en POO Autres “codes”
Aldric Degorre Marquage du texte

Introduction
Programmation
orientée objet • “Blah :” : titre de paragraphe
Java
Guide
• “important” : mot ou passage important
Style

Objets et • “très important” : mot ou passage très important


classes

Types et • “concept” : concept clé (en général défini explicitement ou implicitement dans le
polymorphisme
transparent... sinon, il faudra s’assurer de connaître ou trouver la définition)
Héritage

Généricité • “foreign words” : passage en langue étrangère


Gestion des
erreurs et • “void duCodeJavaEnLigne(){}” : code java intégré au texte
exceptions

Interfaces void duCodeJavaNonIntegre() {
graphiques System.out.println("Java␣c'est␣cool␣!");
Concurrence }

Code non intégré au texte.


Détails
Compléments
en POO Autres “codes”
Aldric Degorre Références bibliographiques

Introduction
Programmation
orientée objet
Java
Guide

Style

Objets et
classes • JLS 8.2 : Java language specification, section 8.2.
Types et
polymorphisme • JVMS 6.5 : Java virtual machine specification, section 6.5.
Héritage
• EJ3 Item 42 : 42ème item de Effective Java 3rd edition (Joshua Bloch).
Généricité

Gestion des • JCiP 3.4 :Java Concurrency in Practice (Brian Goetz), chapitre 3 section 4.
erreurs et
exceptions

Interfaces
graphiques

Concurrence

Détails
Compléments
en POO Question(s) de style
Aldric Degorre

Introduction

Style
Noms
Métrique
• Aucun programme n’est écrit directement dans sa version définitive.
Commentaires
Patrons de conception • Il doit donc pouvoir être facilement modifié par la suite.
Objets et
classes • Pour cela, ce qui est déjà écrit doit être lisible et compréhensible.
Types et • lisible par le programmeur d’origine
polymorphisme
• lisible par l’équipe qui travaille sur le projet
Héritage
• lisible par toute personne susceptible de travailler sur le code source (pour le logiciel
Généricité
libre : la Terre entière !)
Gestion des
erreurs et
exceptions

Interfaces
Les commentaires 28 et la javadoc peuvent aider, mais rien ne remplace un code source
graphiques bien écrit.
Concurrence

28. Si un code source contient plus de commentaires que de code, c’est en réalité assez “louche”.
Compléments
en POO Question de goût ?
Aldric Degorre

Introduction

Style
Noms

• “être lisible” → évidemment très subjectif


Métrique
Commentaires
Patrons de conception

Objets et • un programme est lisible s’il est écrit tel qu’“on” a l’habitude de les lire
classes

Types et • → habitudes communes prises par la plupart des programmeurs Java (d’autres
polymorphisme
prises par seulement par telle ou telle organisation ou communauté)
Héritage

Généricité
Langage de programmation → comme une langue vivante !
Gestion des
erreurs et
exceptions Il ne suffit pas de connaître par cœur le livre de grammaire pour être compris des
Interfaces locuteurs natifs (il faut aussi prendre l’accent et utiliser les tournures idiomatiques).
graphiques

Concurrence
Compléments
en POO Une hiérarchie de normes
Aldric Degorre

Introduction
Habitudes dictées par :
Style
Noms 1 le compilateur (la syntaxe de Java 29 )
Métrique
Commentaires
Patrons de conception
2 le guide 30 de style qui a été publié par Sun en même temps que le langage Java (→
Objets et conventions à vocation universelle pour tout programmeur Java)
classes

Types et
3 les directives de son entreprise/organisation
polymorphisme

Héritage
4 les directives propres au projet
Généricité … et ainsi de suite (il peut y avoir des conventions internes à un package, à une classe,
Gestion des
erreurs et
etc.)
exceptions

Interfaces
» et enfin... le bon sens ! 31
graphiques

Concurrence Nous parlerons principalement du 2ème point et des conventions les plus communes.
29. L’équivalent du livre de grammaire dans l’analogie avec la langue vivante.
30. À rapprocher des avis émis par l’Académie Française ?
31. Mais le bon sens ne peut être acquis que par l’expérience.
Compléments
en POO Nommer les entités
Aldric Degorre (classes, méthodes, variables, ...)

Introduction

Style Règles de capitalisation pour les noms (auxquelles on ne déroge pratiquement jamais) :
Noms
Métrique
Commentaires • ... de classes, interfaces, énumérations et annotations 32 −→ UpperCamelCase
Patrons de conception

Objets et • ... de variables (locales et attributs), méthodes −→ lowerCamelCase


classes

Types et • ... de constantes (static final ou valeur d’enum) −→


polymorphisme

Héritage
SCREAMING_SNAKE_CASE
Généricité • ... de packages −→ tout en minuscules sans séparateur de mots 33 . Exemple :
Gestion des
erreurs et
com.masociete.biblitothequetruc 34 .
exceptions

Interfaces → rend possible de reconnaître à la première lecture quel genre d’entité un nom désigne.
graphiques

Concurrence

32. c.-à-d. tous les types référence


33. “_” autorisé si on traduit des caractères invalides, mais pas spécialement encouragé
34. pour une bibliothèque éditée par une société dont le nom de domaine internet serait masociete.com
Compléments
en POO Nommer les entités
Aldric Degorre Codage, et langue

Introduction
• Se restreindre aux caractères suivants :
Style
Noms • a-z, A-Z : les lettres minuscules et capitales (non accentuées),
Métrique
Commentaires • 0-9 : les chiffres,
Patrons de conception
• _ : le caractère soulignement (seulement pour snake_case).
Objets et
classes
Explication :
Types et
polymorphisme • $ (dollar) est autorisé mais réservé au code automatiquement généré ;
Héritage • les autres caractères ASCII sont réservés (pour la syntaxe du langage) ;
Généricité • la plupart des caractères unicode non-ASCII sont autorisés (p. ex. caractères
Gestion des
erreurs et
accentués), mais aucun standard de codage imposé pour les fichiers .java. 35
exceptions

Interfaces • Interdits : commencer par 0-9 ; prendre un nom identique à un mot-clé réservé.
graphiques

Concurrence • Recommandé : Utiliser l’Anglais américain (pour les noms utilisés dans le
programme et les commentaires et la javadoc).
35. Or il en existe plusieurs. En ce qui vous concerne : il est possible que votre PC personnel et celle de la
salle de TP n’aient pas le même réglage par défaut → incompatibilité du code source.
Compléments
en POO Nommer les entités
Aldric Degorre Nature grammaticale (1)

Introduction Nature grammaticale des identifiants :


Style
Noms • types (→ notamment noms des classes et interfaces) : nom au singulier
Métrique
Commentaires
Patrons de conception
ex : String, Number, List, ...
Objets et • classes-outil (non instanciables, contenu statique seulement) : nom au pluriel
classes

Types et ex : Arrays, Objects, Collections, ... 36


polymorphisme
• variables : nom, singulier sauf pour collections ; éventuellement adjectif ou verbe
Héritage

Généricité
(participe présent ou passé) pour booléen. ex :
Gestion des int count = 0; // noun (singular)
erreurs et boolean finished = false; // past participle
exceptions
while (!finished) {
Interfaces finished = ...;
graphiques
...
Concurrence count++;
...
}

36. attention, il y a des contre-exemples au sein même du JDK : System, Math... oh !


Compléments
en POO Nommer les entités
Aldric Degorre Nature grammaticale (2)

Introduction Les noms de méthodes contiennent généralement un verbe, qui est :


Style
Noms
Métrique
• get si c’est un accesseur en lecture (“getteur”) ; ex : String getName();
Commentaires
Patrons de conception
• is si c’est un accesseur en lecture d’une propriété booléenne ;
Objets et
classes
ex : boolean isInitialized();
Types et • set si c’est un accesseur en écriture (“setteur”) ;
polymorphisme

Héritage
ex : void getName(String name);
Généricité • tout autre verbe, à l’indicatif, si la méthode retourne un booléen (méthode prédicat) ;
Gestion des
erreurs et
• à l’impératif 37 , si la méthode sert à effectuer une action avec effet de bord 38
exceptions
Arrays.sort(myArray);
Interfaces
graphiques • au participe passé si la méthode retourne une version transformée de l’objet, sans
Concurrence
modifier l’objet (ex : list.sorted()).
37. ou infinitif sans le “to”, ce qui revient au même en Anglais
38. c.-à-d. mutation de l’état ou effet physique tel qu’un affichage ; cela s’oppose à fonction pure qui
effectue juste un calcul et en retourne le résultat
Compléments
en POO Nommer les entités
Aldric Degorre Concision versus information

Introduction

Style • Pour tout idenficateur, il faut trouver le bon compromis entre information (plus long)
Noms
Métrique et facilité à l’écrire (plus court).
Commentaires
Patrons de conception
• Typiquement, plus l’usage est fréquent et local, plus le nom est court :
Objets et
classes ex. : variables de boucle
Types et
polymorphisme
for (int idx = 0; idx < anArray.length; idx++){ ... }
Héritage • plus l’usage est lointain de la déclaration, plus le nom doit être informatif
Généricité
(sont particulièrement concernés : classes, membres publics... mais aussi les
Gestion des
erreurs et paramètres des méthodes !)
exceptions

Interfaces
ex. : paramètres de constructeur Rectangle(double centerX, double
graphiques centerY, double width, double length){ ... }
Concurrence

Toute personne lisant le programme s’attend à une telle stratégie → ne pas l’appliquer
peut l’induire en erreur.
Compléments
en POO Nombre de caractères par ligne
Aldric Degorre
• On limite le nombre de caractères par ligne de code. Raisons :
Introduction
• certains programmeurs préfèrent désactiver le retour à la ligne automatique 39 ;
Style
Noms • même la coupure automatique ne se fait pas forcément au meilleur endroit ;
Métrique
Commentaires
• longues lignes illisibles pour le cerveau humain (même si entièrement affichées) ;
Patrons de conception
• certains programmeurs aiment pouvoir afficher 2 fenêtres côte à côte.
Objets et
classes • Limite traditionnelle : 70 caractères/ligne (les vieux terminaux ont 80 colonnes 40 ).
Types et
polymorphisme De nos jours (écrans larges, haute résolution), 100-120 est plus raisonnable 41 .
Héritage
• Arguments contre des lignes trop petites :
Généricité

Gestion des
• découpage trop élémentaire rendant illisible l’intention globale du programme ;
erreurs et • incitation à utiliser des identifiants plus courts pour pouvoir écrire ce qu’on veut en
exceptions

Interfaces
une ligne (→ identifiants peu informatifs, mauvaise pratique).
graphiques
39. De plus, historiquement, les éditeurs de texte n’avaient pas le retour à la ligne automatique
Concurrence
40. Et d’où vient ce nombre 80 ? C’est le nombre du de colonnes dans le standard de cartes perforées d’IBM
inventé en... 1928 ! Et pourquoi ce choix en 1928 ? Parce que les machines à écrire avaient souvent 80
colonnes... bref c’est de l’histoire très ancienne !
41. Selon moi, mais attention, c’est un sujet de débat houleux !
Compléments
en POO Indentation
Aldric Degorre
• Indenter = mettre du blanc en tête de ligne pour souligner la structure du
Introduction

Style
programme. Ce blanc est constitué d’un certain nombre d’indentations.
Noms
Métrique
• En Java, typiquement, 1 indentation = 4 espaces (ou 1 tabulation).
Commentaires
Patrons de conception
• Le nombre d’indentations est égal à la profondeur syntaxique du début de la ligne ≃
Objets et nombre de paires de symboles 42 ouvertes mais pas encore fermées. 43
classes

Types et
• Tout éditeur raisonnablement évolué sait indenter automatiquement (règles
polymorphisme
paramétrables dans l’éditeur). Pensez à demander régulièrement l’indentation
Héritage
automatique, afin de vérifier qu’il n’y a pas d’erreur de structure !
Généricité

Gestion des
erreurs et
Exemple :
exceptions
voici un exemple (
Interfaces qui n'est pas du Java;
graphiques mais suit ses "conventions
Concurrence d'indentation"
)

42. Parenthèses, crochets, accolades, guillemets, chevrons, ...


43. Pas seulement : les règles de priorité des opérations créent aussi de la profondeur syntaxique.
Compléments
en POO Où couper les lignes
Aldric Degorre

Introduction

Style
Noms
• On essaye de privilégier les retours à la ligne en des points du programme “hauts”
Métrique
Commentaires
dans l’arbre syntaxique (→ minimise la taille de l’indentation).
P. ex., dans “(x + 2) ∗ (3 − 9/2)”, on préfèrera couper à côté de “∗” →
Patrons de conception

Objets et
classes
(x + 2)
Types et * (3 - 9 / 2)
polymorphisme

Héritage

Généricité
• Parfois difficile à concilier avec la limite de caractères par ligne → compromis
Gestion des nécessaires.
erreurs et
exceptions • → pour le lieu de coupure et le style d’indentation, essayez juste d’être raisonnable
Interfaces
graphiques et consistent. Dans le cadre d’un projet en équipe, se référer aux directives du
Concurrence projet.
Compléments
en POO Taille des classes
Aldric Degorre

Introduction

Style
Noms
• Y a-t-il une bonne taille pour une classe ?
Métrique
Commentaires • Déjà, plusieurs critères de taille : nombre de lignes, nombre de méthodes, ....
Patrons de conception

Objets et • Le découpage en classes est avant tout guidé par l’abstraction objet retenue pour
classes

Types et
modéliser le problème qu’on veut résoudre.
polymorphisme
• En pratique, une classe trop longue est désagréable à utiliser. Ce désagrément
Héritage

Généricité
traduit souvent une décomposition insuffisante de l’abstraction.
Gestion des • Conseil : se fixer une limite de taille et décider, au cas par cas, si et comment il faut
erreurs et
exceptions “réparer” les classes qui dépassent la limite (cela incite à améliorer l’aspect objet
Interfaces
graphiques
du programme).
Concurrence • En général, pour un projet en équipe, suivre les directives du projet.
Compléments
en POO Taille des méthodes
Aldric Degorre

Introduction
• Pour une méthode, la taille est le nombre de lignes.
Style
Noms
Métrique
• Principe de responsabilité unique 44 : une méthode est censée effectuer une tâche
Commentaires
Patrons de conception
précise et compréhensible.
Objets et
→ Un excès de lignes
classes
• nuit à la compréhension ;
Types et
polymorphisme
• peut traduire le fait que la méthode effectue en réalité plusieurs tâches probablement
Héritage séparables.
Généricité • Quelle est la bonne longueur ?
Gestion des
erreurs et • Mon critère 45 : on ne peut pas bien comprendre une méthode si on ne peut pas la
exceptions
parcourir en un simple coup d’œil
Interfaces
graphiques → faire en sorte qu’elle tienne en un écran (∼ 30-40 lignes max.)
Concurrence
• En général, suivre les directives du projet.

44. Le “S” de la méthodologie SOLID.


45. qui n’engage que moi !
Compléments
en POO Nombre de paramètres des méthodes
Aldric Degorre
Autre critère : le nombre de paramètres.
Introduction

Style
Trop de paramètres (>4) implique :
Noms
Métrique
Commentaires
• Une signature longue et illisible.
• Une utilisation difficile (“ah mais ce paramètre là, il était en 5e ou en 6e position,
Patrons de conception

Objets et
classes déjà ?”)
Types et
polymorphisme
Il est souvent possible de réduire le nombre de paramètres
Héritage

Généricité • en utilisant la surcharge,


Gestion des
erreurs et
• ou bien en séparant la méthode en plusieurs méthodes plus petites (en
exceptions
décomposant la tâche effectuée),
Interfaces
graphiques • ou bien en passant des objets composites en paramètre
Concurrence
ex : un Point p au lieu de int x, int y.
Voir aussi : patron “monteur” (le constructeur prend pour seul paramètre une
instance du Builder).
Compléments
en POO Et ainsi de suite
Aldric Degorre

Introduction

Style
Noms
Métrique
Commentaires
Patrons de conception • Pour chaque composant contenant des sous-composants, la question “combien de
Objets et
classes sous-composants ?” se pose.
Types et
polymorphisme
• “Combien de packages dans un projet ?”
Héritage “Combien de classes dans un package ?”
Généricité
• Dans tous les cas essayez d’être raisonnable et homogène/consistent (avec
Gestion des
erreurs et vous-même... et avec l’organisation dans laquelle vous travaillez).
exceptions

Interfaces
graphiques

Concurrence
Compléments
en POO Commentaires
Aldric Degorre Plusieurs sortes de commentaires (1)

Introduction

Style
• En ligne :
Noms
Métrique int length; // length of this or that
Commentaires
Patrons de conception

Objets et Pratique pour un commentaire très court tenant sur une seule ligne (ou ce qu’il en
classes
reste... )
Types et
polymorphisme
• en bloc :
Héritage

Généricité /*
* Un commentaire un peu plus long.
Gestion des
erreurs et
* Les "*" intermédiaires ne sont pas obligatoires, mais Eclipse
exceptions * les ajoute automatiquement pour le style. Laissez-les !
*/
Interfaces
graphiques

Concurrence À utiliser quand vous avez besoin d’écrire des explications un peu longues, mais
que vous ne souhaitez pas voir apparaître dans la documentation à proprement
parler (la JavaDoc).
Compléments
en POO Commentaires
Aldric Degorre Plusieurs sortes de commentaires (2)

Introduction

Style
Noms
Métrique
Commentaires
Patrons de conception
• en bloc JavaDoc :
Objets et /**
classes
* Returns an expression equivalent to current expression, in which
Types et * every occurrence of unknown var was substituted by the expression
polymorphisme
* specified by parameter by.
Héritage *
Généricité * @param var variable that should be substituted in this expression
* @param by expression by which the variable should be substituted
Gestion des
erreurs et * @return the transformed expression
exceptions */
Interfaces Expression subst(UnknownExpr var, Expression by);
graphiques

Concurrence
Compléments
en POO Commentaires
Aldric Degorre La JavaDoc

Introduction

Style
À propos de la JavaDoc :
Noms
Métrique
Commentaires
• Les commentaires au format JavaDoc sont compilables en documentation au
Patrons de conception
format HTML (dans Eclipse : menu “Project”, “Generate JavaDoc...”).
Objets et
classes • Pour toute déclaration de type (classe, interface, enum... ) ou de membre (attribut,
Types et
polymorphisme constructeur, méthode), un squelette de documentation au bon format (avec les
Héritage bonnes balises) peut être généré avec la combinaison Alt+Shift+J (toujours
Généricité dans Eclipse).
Gestion des
erreurs et • Il est indispensable de documenter tout ce qui est public.
exceptions

Interfaces
• Il est fortement recommandé de documenter tout ce qui n’est pas privé (car
graphiques
utilisable par d’autres programmeurs, qui n’ont pas accès au code source).
Concurrence
• Il est utile de documenter ce qui est privé, pour soi-même et les autres membres de
l’équipe.
Compléments
en POO Patrons de conception (1)
Aldric Degorre ou design patterns

Introduction

Style
Noms
Métrique
Commentaires
• Analogie langage naturel : patron de conception = figure de style
Patrons de conception

Objets et
• Ce sont des stratégies standardisées et éprouvées pour arriver à une fin.
classes
ex : créer des objets, décrire un comportement ou structurer un programme
Types et
polymorphisme • Les utiliser permet d’éviter les erreurs les plus courantes (pour peu qu’on utilise le
Héritage
bon patron !) et de rendre ses intentions plus claires pour les autres programmeurs
Généricité
qui connaissent les patrons employés.
Gestion des
erreurs et
exceptions
• Connaître les noms des patrons permet d’en discuter avec d’autres
Interfaces programmeurs. 46
graphiques

Concurrence

46. De la même façon qu’apprendre les figures de style en cours de Français, permet de discuter avec
d’autres personnes de la structure d’un texte...
Compléments
en POO Patrons de conception (2)
Aldric Degorre ou design patterns

Introduction

Style
Noms
Métrique • Quelques exemples dans le cours : décorateur, délégation, observateur/observable,
Commentaires
Patrons de conception monteur.
Objets et
classes • Patrons les plus connus décrits dans le livre du “Gang of Four” (GoF) 47
Types et
polymorphisme • Les patrons ne sont pas les mêmes d’un langage de programmation à l’autre :
Héritage • les patrons implémentables dépendent de ce que la syntaxe permet
Généricité • les patrons utiles dépendent aussi de ce que la syntaxe permet :
Gestion des quand un langage est créé, sa syntaxe permet souvent de traiter simplement des
erreurs et
exceptions situations qui autrefois nécessitaient l’usage d’un patron (moins simple).
Interfaces Probablement, le concept de déclarer des classes est apparu comme cela.
graphiques

Concurrence

47. E. Gamma, R. Helm, R. Johnson and J. Vlissides, Design Patterns : Elements of Reusable Object-oriented
Software, 1995, Addison-Wesley Longman Publishing Co., Inc.
Compléments
en POO Objets
Aldric Degorre Vision haut niveau

Introduction

Style
• Un objet est “juste” un nœud dans le graphe de communication qui se déploie
Objets et
classes
Objets et classes
quand on exécute un programme OO.
• Il est caractérisé par une certaine interface 48 de communication.
Membres et contextes
Encapsulation
Types imbriqués

Types et • Un objet a un état (modifiable ou non), en grande partie caché vis-à-vis des autres
polymorphisme

Héritage objets (l’état est encapsulé).


Généricité • Le graphe de communication est dynamique, ainsi, les objets naissent (sont
Gestion des
erreurs et instanciés) et meurent (sont détruits, désalloués).
exceptions

Interfaces
graphiques

Concurrence

... oui mais concrètement ?

Analyse 48. Au moins implicitement : ici, “interface” ne désigne pas forcément la construction interface de Java.
Compléments
en POO Objets
Aldric Degorre Vision bas niveau (en Java)

Introduction

Style
Object = entité...
Objets et
classes • caractérisée par un enregistrement contigü de données typées (attributs 49 )
Objets et classes
Membres et contextes
Encapsulation
• accessible via une référence 50 vers cet enregistrement ;
Types imbriqués

Types et • manipulable/interrogeable via un ensemble de méthodes qui lui est propre.


polymorphisme

Héritage classe de l’objet : réf. 7→ Personne.class


Généricité int age 42
La variable référence référence
7−→ l’objet réf. 7→ chaîne "Toto"
Gestion des String nom
erreurs et Personne toto ...
exceptions
boolean marie true
Interfaces
graphiques

Concurrence
Type d’un objet : défini par sa classe, qui spécifie ses attributs et ses méthodes.
49. On dit aussi champs, comme pour les struct de C/C++.
Pour la représentation mémoire, un objet et une instance de struct sont similaires.
50. Il s’agit en vrai d’un pointeur. Les références de C++ sont un concept (un peu) différent.
Compléments
en POO Classes
Aldric Degorre Langages à prototypes, langages à classes

Introduction

Style

Objets et
classes
Objets et classes
• Besoin : créer de nombreux objets similaires (même interface, même schéma de
Membres et contextes
Encapsulation
données).
Types imbriqués

Types et
• 2 solutions → 2 familles de LOO :
polymorphisme
• LOO à classes (Java et la plupart des LOO) : les objets sont instanciés à partir de la
Héritage

Généricité description donnée par une classe ;


Gestion des • LOO à prototypes (toutes les variantes d’ECMAScript dont JavaScript ; Self,
erreurs et
exceptions Lisaac, ...) : les objets sont obtenus par extension d’un objet existant (le prototype).
Interfaces
graphiques

Concurrence
→ l’existence de classes n’est pas une nécessité en POO

Analyse
Compléments
en POO Classes
Aldric Degorre Exemple

Introduction Pour l’objet juste donné en exemple, la classe Personne pourrait être :
Style
public class Personne {
Objets et // attributs
classes
Objets et classes private String nom; private int age; private boolean marie;
Membres et contextes
Encapsulation
Types imbriqués
// constructeur
public Personne(String nom, int age, boolean marie) {
Types et
polymorphisme
this.nom = nom; this.age = age; this.marie = marie;
}
Héritage

Généricité // méthodes (ici : accesseurs)


Gestion des public String getNom() { return nom; }
erreurs et public void setNom(String nom) { this.nom = nom; }
exceptions

Interfaces public int getAge() { return age; }


graphiques public void setAge(int age) { this.age = age; }
Concurrence
public boolean getMarie() { return marie; }
public void setMarie(boolean marie) { this.marie = marie; }
}
Exemple
Compléments
en POO Classes
Aldric Degorre Points de vue pertinents pour Java

Introduction

Style

Objets et
classes Classe = patron/modèle/moule/... pour définir des objets similaires 51 .
Objets et classes
Membres et contextes
Encapsulation
Autres points de vue :
Types imbriqués

Types et
Classe =
polymorphisme

Héritage • ensemble cohérent de définitions (champs, méthodes, types auxiliaires, ...), en


Généricité principe relatives à un même type de données
Gestion des
erreurs et • conteneur permettant l’encapsulation (= limite de visibilité des membres privés). 52
exceptions

Interfaces
graphiques

Concurrence

51. “similaires” = utilisables de la même façon (même type) et aussi structurés de la même façon.
52. Remarque : en Java, l’encapsulation se fait par rapport à la classe et au paquetage et non par rapport à
Révision l’objet. En Scala, p. ex., un attribut peut avoir une visibilité limitée à l’objet qui le contient.
Compléments
en POO Classes
Aldric Degorre Autres points de vue (non-OO) :

Introduction

Style

Objets et
classes
Classe =
Objets et classes
Membres et contextes
Encapsulation
• sous-division syntaxique du programme
Types imbriqués

Types et
• espace de noms (définitions de nom identique possibles si dans classes
polymorphisme
différentes)
Héritage

Généricité
• parfois, juste une bibliothèque de fonctions statiques, non instanciable 53
Gestion des exemples de classes non instanciables du JDK : System, Arrays, Math, ...
erreurs et
exceptions

Interfaces
Les aspects ci-dessus sont réels en Java, mais ne retenir que ceux-ci serait manquer
graphiques l’essentiel (i.e. : classe → concept de POO).
Concurrence

Révision 53. Java force à tout définir dans des classes → encourage cet usage détourné de la construction class.
Compléments
en POO Instanciation
Aldric Degorre De la classe à l’objet

Introduction

Style

Objets et • Une classe permet de “fabriquer” plusieurs objets selon un même modèle : les
instances 54 de la classe.
classes
Objets et classes
Membres et contextes
Encapsulation
Types imbriqués
• Ces objets ont le même type, dont le nom est celui de la classe.
Types et
polymorphisme
• La fabrication d’un objet s’appelle l’instanciation. Celle-ci consiste à
Héritage • réserver la mémoire (≃ malloc en C)
Généricité • initialiser les données 55 de l’objet
Gestion des
erreurs et • On instancie la classe Truc via l’expression “new Truc(params)”, dont la valeur
exceptions

Interfaces
est une référence vers un objet de type Truc nouvellement créé. 56
graphiques

Concurrence

54. En POO, “instance” et “objet” sont synonymes. Le mot “instance” souligne l’appartenance à un type.
55. En l’occurence : les attributs d’instance déclarés dans cette classe.
Révision 56. Ainsi, on note que le type défini par une classe est un type référence.
Compléments
en POO Instanciation
Aldric Degorre Constructeurs (1)

Introduction Constructeur : “genre de” méthode 57 servant à construire une instance d’une classe.
Style

Objets et • Déclaration :
classes
Objets et classes
MaClasse(/* paramètres */) {
Membres et contextes
Encapsulation // instructions ; ici "this" désigne l'objet en construction
Types imbriqués }
Types et
polymorphisme
NB : même nom que la classe, pas de type de retour, ni de return dans son corps.
Héritage

Généricité • Typiquement, “// instructions” = initialisation des attributs de l’instance.


Gestion des
erreurs et • Appel toujours précédé du mot-clé new :
exceptions

Interfaces
MaClasse monObjet = new MaClasse(... parametres... );
graphiques

Concurrence Cette instruction déclare un objet monObjet, crée une instance de MaClasse et
l’affecte à monObjet.
57. En toute rigueur, un constructeur n’est pas une méthode. Notons tout de même les similarités dans les
Détails syntaxes de déclaration et d’appel et dans la sémantique (exécution d’un bloc de code).
Compléments
en POO Instanciation
Aldric Degorre Constructeurs (2)

Introduction

Style Il est possible de :


Objets et
classes
Objets et classes
• définir plusieurs constructeurs (tous le même nom → cf. surcharge) ;
Membres et contextes
Encapsulation • définir un constructeur secondaire à l’aide d’un autre constructeur déjà défini : sa
Types imbriqués

Types et
première instruction doit alors être this(paramsAutreConstructeur); 58 ;
polymorphisme

Héritage
• ne pas écrire de constructeur :
Généricité • Si on ne le fait pas, le compilateur ajoute un constructeur par défaut sans
Gestion des
erreurs et
paramètre. 59 .
exceptions • Si on a écrit un constructeur, alors il n’y a pas de constructeur par défaut 60 .
Interfaces
graphiques

Concurrence
58. Ou bien super(params); si utilisation d’un constructeur de la superclasse.
59. Les attributs restent à leur valeur par défaut (0, false ou null), ou bien à celle donnée par leur
initialiseur, s’il y en a un.
Détails 60. Mais rien n’empêche d’écrire, en plus, à la main, un constructeur sans paramètre.
Compléments
en POO Membres d’une classe
Aldric Degorre

Introduction

Style Le corps d’une classe consiste en une séquence de définitions : constructeurs 61 et


Objets et
classes
membres de la classe.
Objets et classes
Membres et contextes
Encapsulation
Plusieurs catégories de membres : attributs, méthodes et types membres 62 .
Types imbriqués

Types et
Un membre peut être
polymorphisme

Héritage • soit statique (relatif à la classe) → mot-clé static,


Généricité
• soit non statique ou d’instance (relatif à une instance de la classe) → pas mot-clé.
Gestion des
erreurs et
exceptions
Les membres d’un objet donné sont les membres non statiques de la classe de l’objet.
Interfaces
graphiques

Concurrence

61. D’après la JLS 8.2, les constructeurs ne sont pas des membres. Néanmoins, sont déclarés à l’intérieur
d’une classe et acceptent, comme les membres, les modificateurs de visibilité (private, public, ...).
Révision 62. Souvent abusivement appelés “classes internes”.
Compléments
en POO Membres d’une classe
Aldric Degorre Exemple

Introduction p u b l i c class Personne {


// a t t r i b u t s
Style p u b l i c s t a t i c i n t derNumINSEE = 0 ;
p u b l i c f i n a l NomComplet nom ;
Objets et p u b l i c f i n a l i n t numInsee ;
classes
Objets et classes
// constructeur
Membres et contextes
p u b l i c P e r s o n n e ( S t r i n g nom , S t r i n g prenom ) {
Encapsulation
t h i s . nom = new NomComplet ( nom , prenom ) ;
Types imbriqués
t h i s . n u m I n s e e = ++ derNumINSEE ;
Types et }
polymorphisme
/ / mé t h o d e
Héritage public String toString () {
r e t u r n S t r i n g . f o r m a t ( ” %s␣%s␣ (% d ” ) , nom . nom , nom . prenom , n u m I n s e e ) ;
Généricité }
Gestion des / / e t même . . . c l a s s e i m b r i q u é e !
erreurs et p u b l i c s t a t i c f i n a l c l a s s NomComplet {
exceptions p u b l i c f i n a l S t r i n g nom ;
p u b l i c f i n a l S t r i n g prenom ;
Interfaces
graphiques
p r i v a t e NomComplet ( S t r i n g nom , S t r i n g prenom ) {
Concurrence t h i s . nom = nom ;
t h i s . prenom = prenom ;
}
}
}
Exemple
Compléments
en POO Membres d’une classe
Aldric Degorre Accéder à un membre : où et comment ? → notion de contexte

Introduction

Style
Contexte (associé à tout point du code source) :
Objets et
classes • dans une définition 63 statique : contexte = la classe contenant la définition ;
Objets et classes
Membres et contextes
Encapsulation
• dans une définition non-statique : contexte = l’objet “courant”, le récepteur 64 .
Types imbriqués

Types et Désigner un membre m déjà défini quelque part :


polymorphisme

Héritage • écrire soit juste m (nom simple), soit chemin.m (nom qualifié)
Généricité

Gestion des
• “chemin” donne le contexte auquel appartient le membre m :
erreurs et
exceptions
• pour un membre statique : la classe 65 (ou interface ou autre...) où il est défini
Interfaces
• pour un membre d’instance : une instance de la classe où il est défini
graphiques

Concurrence • “chemin.” est facultatif si chemin == contexte local.


63. typiquement, remplacer “définition” par “corps de méthode”
64. L’objet qui, à cet endroit, serait référencé par this.
Détails 65. Et pour désigner une classe d’un autre paquetage : chemin = paquetage.NomDeClasse.
Compléments
en POO Membres statiques et membres non statiques
Aldric Degorre
En bref : statique = permanent, “global” (mais avec encapsulation possible).
Introduction

Style
Non statique = lié à une instance.
Objets et
classes
statique (ou “de classe”) non statique (ou “d’instance”)
Objets et classes
Membres et contextes
attribut donnée globale 66 , commune à donnée propre 67 à chaque
Encapsulation
Types imbriqués
toutes les instances de la classe. instance (nouvel exemplaire de
Types et cette variable alloué et initialisé à
polymorphisme
chaque instanciation).
Héritage
méthode “fonction”, comme celles des lan- message à objet privilégié : le
Généricité

Gestion des
gages impératifs. récepteur de la méthode (this).
erreurs et
exceptions
type membre simple classe/interface définie à comme statique, mais instances
Interfaces l’intérieur d’une autre classe (per- contenant une référence vers ins-
graphiques
met encapsulation plus précise). tance de la classe englobante.
Concurrence
Remarque : un constructeur relie le non statique (contexte interne) au statique (contexte d’appel).

66. Correspond à variable globale dans d’autres langages.


Résumé
67. Correspond à champ de struct en C.
Compléments
en POO Membres statiques et membres non-statiques
Aldric Degorre Zoom sur le cas des attributs

Introduction Qu’affiche le programme suivant ?


Style
class Element {
Objets et private static int a = 0; private int b = 1;
classes
Objets et classes
public void plusUn() { a++; b++; }
Membres et contextes @Override public String toString() { return "" + a + b; }
Encapsulation
}
Types imbriqués

Types et
public class Compter {
polymorphisme
private static Element e = new Element(), f = new Element();
Héritage public static void main(String [] args) {
Généricité printall(); e.plusUn(); printall(); f.plusUn(); printall();
Gestion des
}
erreurs et private static void printall() { System.out.println("e␣:␣" + e + "␣et␣f␣:␣" + f); }
exceptions }
Interfaces
graphiques

Concurrence

Exemple
Compléments
en POO Membres statiques et membres non-statiques
Aldric Degorre Zoom sur le cas des attributs

Introduction Qu’affiche le programme suivant ?


Style
class Element {
Objets et private static int a = 0; private int b = 1;
classes
Objets et classes
public void plusUn() { a++; b++; }
Membres et contextes @Override public String toString() { return "" + a + b; }
Encapsulation
}
Types imbriqués

Types et
public class Compter {
polymorphisme
private static Element e = new Element(), f = new Element();
Héritage public static void main(String [] args) {
Généricité printall(); e.plusUn(); printall(); f.plusUn(); printall();
Gestion des
}
erreurs et private static void printall() { System.out.println("e␣:␣" + e + "␣et␣f␣:␣" + f); }
exceptions }
Interfaces
graphiques

Concurrence
Réponse :
e : 01 et f : 01
e : 12 et f : 11
e : 22 et f : 22
Exemple
Compléments
en POO Membres statiques et membres non-statiques
Aldric Degorre Zoom sur le cas des méthodes

Introduction
Remarque, on peut réécrire une méthode statique comme non statique de même
Style
comportement, et vice-versa :
Objets et
classes
class C {
Objets et classes
Membres et contextes void f() { instr(this); } // exemple d'appel : x.f()
Encapsulation // et
Types imbriqués
static void g(C that) { instr(that); } // exemple d'appel : C.g(x)
Types et }
polymorphisme

Héritage

Généricité Mais différences essentielles à l’usage :


Gestion des
erreurs et • dans f, this nécessairement de type C alors que, dans g, that aurait pu être
exceptions

Interfaces
déclaré de n’importe quel type (→ on aurait aussi pu déclarer g ailleurs que dans C) ;
graphiques
→ contraintes sur le lieu de définition de f 68 .
Concurrence
• f sujette au mécanisme de liaison dynamique : à l’appel x.f(), avec x instance de
D sous-classe (cf. héritage) de C, la méthode exécutée sera f défini dans D.
Discussion 68. Et donc aussi sur la visibilité/l’encapsulation.
Compléments
en POO Fabriques statiques
Aldric Degorre Un cas d’utilisation intéressant pour les méthodes statiques (EJ3 Item 1)

Introduction Limitations des constructeurs :


Style
• même nom pour tous
Objets et
classes
Objets et classes
→ le nom n’est pas infomatif,
Membres et contextes → impossible d’avoir 2 constructeurs avec la même signature ;
Encapsulation
Types imbriqués • appel à constructeur auxiliaire nécessairement en première instruction ;
Types et
polymorphisme • obligation de retourner une nouvelle instance → pas de contrôle d’instance 69 ;
Héritage • obligation de retourner une instance directe de la classe.
Généricité

Gestion des
En écrivant une fabrique statique on contourne toutes ces limitations :
erreurs et
exceptions
p u b l i c a b s t r a c t c l a s s C { / / ou b i e n i n t e r f a c e f i n a l c l a s s CImpl1 extends C { / / impl é mentation
Interfaces ... p a c k a g e−p r i v a t e ( p o s s i b l e a u s s i : c l a s s e
graphiques // la fabrique : imbriqu ée )
p u b l i c s t a t i c C o f ( D a r g ) { / / ” o f ” : nom l e ...
Concurrence plus courant pour f a b r i q u e s t a t i q u e / / c o n s t r u c t e u r p a c k a g e−p r i v a t e
i f ( a r g . . . ) r e t u r n new C I m p l 1 ( a r g ) ; CImpl1 ( D arg ) { . . . }
else i f ( arg . . . ) return . . . }
else return . . .
}
}
Compléments
en POO Encapsulation
Aldric Degorre Concept essentiel de la POO

Introduction
L’encapsulation
Style

Objets et • = restriction de l’accès externe aux membres d’une classe considérés comme des
classes
Objets et classes
Membres et contextes
détails d’implémentation internes.
• bonne pratique favorisant la pérennité d’une classe : en minimisant la “surface”
Encapsulation
Types imbriqués

Types et
polymorphisme
exposée à ses clients 70 (= en réduisant leur couplage), ses évolutions et son
Héritage
déboguage sont facilités. 71
Généricité • empêche les clients d’accéder à un objet de façon incorrecte ou non prévue. Ainsi,
Gestion des
erreurs et
• la correction d’un programme est plus facile à vérifier (moins d’intéractions à vérifier) ;
exceptions • on peut prouver des invariants de classe 72 faisant intervenir les attributs privés.
Interfaces
graphiques → L’encapsulation rend donc aussi la classe plus fiable.
Concurrence
70. Clients d’une classe : les classes qui utilisent cette classe.
71. En effet : on peut modifier la classe sans modifier ses clients.
72. Différence avec l’item du dessus : ces invariants doivent être vrais quel que soit le contexte d’utilisation
de la classe, pas seulement dans le programme courant.
Compléments
en POO Encapsulation
Aldric Degorre Exemple

Introduction Ce programme respecte-t-il la propriété “le nième appel à next retourne le nième terme de
Style
la suite de Fibonacci” ?
Objets et
classes
Objets et classes
Membres et contextes
Pas bien :
Encapsulation
Types imbriqués public class FiboGen {
public int a = 1, b = 1;
Types et
polymorphisme public int next() {
int ret = a; a = b; b += ret;
Héritage
return ret;
Généricité }
Gestion des }
erreurs et
exceptions

Interfaces Toute autre classe peut interférer en


graphiques

Concurrence
modifiant directement les valeurs de a ou b
→ on ne peut rien prouver !
Exemple
Compléments
en POO Encapsulation
Aldric Degorre Exemple

Introduction Ce programme respecte-t-il la propriété “le nième appel à next retourne le nième terme de
Style
la suite de Fibonacci” ?
Objets et
classes
Objets et classes Pas bien : Bien : (ou presque)
Membres et contextes
Encapsulation
Types imbriqués
public class FiboGen { public class FiboGen {
public int a = 1, b = 1; private int a = 1, b = 1;
Types et
polymorphisme
public int next() { public int next() {
int ret = a; a = b; b += ret; int ret = a; a = b; b += ret;
Héritage return ret; return ret;
Généricité } }
Gestion des } }
erreurs et
exceptions

Interfaces Toute autre classe peut interférer en Seule la méthode next peut modifier
graphiques
modifiant directement les valeurs de a ou b directement les valeurs de a ou b
Concurrence
→ on ne peut rien prouver ! → s’il y a un bug, c’est dans la méthode
next et pas ailleurs ! 73
Exemple
73. Or il y a un bug, en théorie, si on exécute next plusieurs fois simultanément (sur plusieurs threads).
Compléments
en POO Encapsulation
Aldric Degorre Remarque

Introduction

Style

Objets et
classes
Objets et classes
Membres et contextes
Encapsulation
• Au contraire de nombreux autres principes exposés dans ce cours, l’encapsulation
Types imbriqués
ne favorise pas directement la réutilisation de code.
Types et
polymorphisme • À première vue, c’est le contraire : on interdit l’utilisation directe de certaines
Héritage
parties de la classe.
Généricité

Gestion des • En réalité, l’encapsulation augmente la confiance dans le code réutilisé (ce qui,
erreurs et
exceptions indirectement, peut inciter à le réutiliser davantage).
Interfaces
graphiques

Concurrence

Discussion
Compléments
en POO Encapsulation
Aldric Degorre Niveaux de visibilité : private, public, etc.

Introduction L’encapsulation est mise en œuvre via les modificateurs de visibilité des membres.
Style

Objets et
4 niveaux de visibilité en faisant précéder leurs déclarations de private, protected
classes
Objets et classes
ou public ou d’aucun de ces mots (→ visibilité package-private).
sous-classes 74
Membres et contextes
Encapsulation Visibilité classe paquetage partout
Types imbriqués

Types et
private X
polymorphisme
package-private X X
Héritage
protected X X X
Généricité
public X X X X
Gestion des
erreurs et
exceptions Exemple :
Interfaces class A {
graphiques
int x; // visible dans le package
Concurrence private double y; // visible seulement dans A
public final String nom = "Toto"; // visible partout
}

Révision 74. voir héritage


Compléments
en POO Encapsulation
Aldric Degorre En passant : niveaux de visibilité pour les déclarations de premier niveau

Introduction

Style

Objets et
Notion de visibilité : s’applique aussi aux déclarations de premier niveau 75 .
classes
Objets et classes Ici, 2 niveaux seulement : public ou package-private.
Membres et contextes
Encapsulation
Types imbriqués Visibilité paquetage partout
Types et package-private X
polymorphisme

Héritage
public X X
Généricité Rappel : une seule déclaration publique de premier niveau autorisée par fichier. La
Gestion des
erreurs et
classe/interface/... définie porte alors le même nom que le fichier.
exceptions

Interfaces
graphiques
75. Précisions/rappels :
Concurrence
• “premier niveau” = hors des classes, directement dans le fichier ;
• seules les déclarations de type (classes, interfaces, énumérations, annotations) sont concernées.
Compléments
en POO Encapsulation
Aldric Degorre Niveaux de visibilité et documentation

Introduction

Style • Toute déclaration de membre non private est susceptible d’être utilisée par un
Objets et
classes autre programmeur dès lors que vous publiez votre classe.
Objets et classes
Membres et contextes • Elle fait partie de l’API 76 de la classe.
Encapsulation

• → vous devez donc la documenter 77 (EJ3 Item 56)


Types imbriqués

Types et
polymorphisme
• → et vous vous engagez à ne pas modifier 78 sa spécification 79 dans le futur, sous
Héritage

Généricité
peine de “casser” tous les clients de votre classe.
Gestion des
erreurs et Ainsi il faut bien réfléchir à ce que l’on souhaite exposer. 80
exceptions

Interfaces
graphiques
76. Application Programming Interface
77. cf. JavaDoc
Concurrence
78. On peut modifier si ça va dans le sens d’un renforcement compatible.
79. Et, évidemment, à faire en sorte que le comportement réel respecte la spécification !
80. Il faut aussi réfléchir à une stratégie : tout mettre en private d’abord, puis relâcher en fonction des
besoins ? Ou bien le contraire ? Les opinions divergent !
Compléments
en POO Encapsulation
Aldric Degorre Niveaux de visibilité : avertissements

Introduction
Attention, les niveaux de visibilité ne font pas forcément ce à quoi on s’attend.
Style

Objets et
classes • package-private → tout le monde peut ajouter des classes à un paquetage exstant,
Objets et classes
Membres et contextes
même après compilation → garantie faible.
Encapsulation
Types imbriqués • protected → de même et, en +, toute sous-classe, n’importe où, voit la définiton.
Types et
polymorphisme • Aucun niveau ne garantit la confidentialité des données.
Héritage
Constantes : lisibles directement dans le fichier .class.
Généricité

Gestion des
Variables : lisibles, via réflexion, par tout programme s’exécutant sur la même JVM.
erreurs et
exceptions Si la sécurité importe : bloquer la réflexion en utilisant un SecurityManager 81 .
Interfaces
graphiques
L’encapsulation sert à éviter certaines erreurs de programmation mais n’est en aucun
Concurrence
cas un outil de sécurité ! 82
81. Hors programme.
82. Méditer la différence entre sûreté (safety) et sécurité (security) en informatique. Attention, cette
Discussion distinction est souvent faite, mais selon le domaine de métier, la distinction est différente, voire inversée !
Compléments
en POO Encapsulation
Aldric Degorre Java 9 et modules

Introduction

Style

Objets et
classes
Objets et classes
Membres et contextes
Encapsulation
Types imbriqués
Pour être complet sur la visibilité :
Types et
polymorphisme
Java 9 a introduit un nouveau système de modules
Héritage
(Java Platform Module System / JPMS) permettant un contrôle plus strict de la visibilité
Généricité (et bien d’autres choses).
Gestion des
erreurs et
Renseignez-vous !
exceptions

Interfaces
graphiques

Concurrence

Supplément
Compléments
en POO Encapsulation
Aldric Degorre Accesseurs (get, set, ...) et propriétés (1)

Introduction

Style
• Pour les classes publiques, il est recommandé 83 de mettre les attributs en
Objets et private et de donner accès aux données de l’objet en définissant des méthodes
classes
Objets et classes
public appelées accesseurs.
Membres et contextes
Encapsulation • Par convention, on leur donne des noms explicites :
Types imbriqués

Types et • public T getX() 84 : retourne la valeur de l’attribut x (“getteur”).


polymorphisme
• public void setX(T nx) : affecte la valeur nx a l’attribut x (“setteur”).
Héritage

Généricité • Le couple getX et setX définit la propriété 85 x de l’objet qui les contient.
Gestion des
erreurs et • Il existe des propriétés en lecture seule (si juste getteur) et en lecture/écriture
exceptions

Interfaces
(getteur et setteur).
graphiques
83. EJ3 Item 16 : “In public classes, use accessor methods, not public fields”
Concurrence
84. Variante : public boolean isX(), seulement si T est boolean.
85. Terminologie utilisée dans la spécification JavaBeans pour le couple getteur+setteur. Dans nombre de
LOO (C#, Kotlin, JavaScript, Python, Scala, Swift, ...), les propriétés sont cependant une sorte de membre à
part entière supportée par le langage.
Compléments
en POO Encapsulation
Aldric Degorre Accesseurs (get, set, ...) et propriétés (2)

Introduction

Style

Objets et
• Une propriété se base souvent sur un attribut (privé), mais d’autres
classes
Objets et classes
implémentations sont possibles. P. ex. :
Membres et contextes
Encapsulation // propriété "numberOfFingers" :
Types imbriqués
public getNumberOfFingers() { return 10; }
Types et
polymorphisme

Héritage
(accès en lecture seule à une valeur constante → on retourne une expression
Généricité constante)
Gestion des
erreurs et
• Les accesseurs permettent d’affiner les modalités d’accès à la propriété, sans
exceptions
impacter l’interface publique 86 d’accès à la propriété.
Interfaces
graphiques Ainsi on pourra changer ces modalités plus tard, sans modifier les autres classes
Concurrence qui accèdent à la propriété.

86. ici, le couple de méthodes getX()/setX()


Compléments
en POO Encapsulation
Aldric Degorre Accesseurs (get, set, ...) et propriétés (3)

Introduction

Style
Exemple : propriété en lecture/écriture avec contrôle validité des données.
Objets et public final class Person {
classes
Objets et classes
Membres et contextes // propriété "age"
Encapsulation
Types imbriqués
// attribut de base (qui doit rester positif)
Types et private int age;
polymorphisme

Héritage // getteur, accesseur en lecture


Généricité public int getAge() {
return age;
Gestion des
erreurs et }
exceptions

Interfaces // setteur, écriture contrôlée


graphiques public void setAge(int a) {
Concurrence if (a >= 0) age = a;
}
}

Exemple
Compléments
en POO Encapsulation
Aldric Degorre Accesseurs (get, set, ...) et propriétés (4)

Introduction

Style
Exemple : propriété en lecture seule avec évaluation paresseuse.
Objets et
classes public final class Entier {
Objets et classes
Membres et contextes
public Entier(int valeur) { this.valeur = valeur; }
Encapsulation
Types imbriqués
private final int valeur;
Types et
polymorphisme // propriété ``diviseurs'' :
Héritage private List<Integer> diviseurs;
Généricité
public List<Integer> getDiviseurs() {
Gestion des
erreurs et
if (diviseurs == null) diviseurs =
exceptions Collections.unmodifiableList(Outils.factorise(valeur)); // <- calcul
coûteux, à n'effectuer que si nécessaire
Interfaces
graphiques return diviseurs;
}
Concurrence
}

Exemple
Compléments
en POO Encapsulation
Aldric Degorre Accesseurs (get, set, ...) et propriétés (5)

Introduction

Style

Objets et Comportements envisageables pour get et set :


classes
Objets et classes
Membres et contextes • contrôle de validité avant modification ;
Encapsulation
Types imbriqués
• initialisation paresseuse : la valeur de la propriété n’est calculée que lors du premier
Types et
polymorphisme accès (et non dès la construction de l’objet) ;
Héritage
• consignation dans un journal pour déboguage ou surveillance ;
Généricité

Gestion des • observabilité : le setteur notifie les objets observateurs lors des modifications ;
erreurs et
exceptions • vétoabilité : le setteur n’a d’effet que si aucun objet (dans une liste connue de
Interfaces
graphiques “véto-eurs”) ne s’oppose au changement ;
Concurrence • ...
Compléments
en POO Encapsulation
Aldric Degorre Aliasing : les niveaux de visibilité ne garantissent pas, seuls, l’encapsulation !

Introduction

Style

Objets et
classes
Objets et classes
Aliasing = existence de références multiples vers un même objet.
Membres et contextes
Encapsulation
Types imbriqués

Types et
ref1 ◦−→ objet ←−◦ ref2
polymorphisme

Héritage

Généricité
L’éventualité d’un alias extérieur à la classe pour un attribut mutable annule le bénéfice
Gestion des
de l’encapsulation (cela revient presque à laisser l’attribut en public).
erreurs et
exceptions En effet, aucun invariant 87 faisant intervenir un tel attribut n’est prouvable quel que soit
Interfaces
graphiques
le contexte.
Concurrence

Discussion 87. Invariant : propriétée censée restée vraie tout au long de l’exécution.
Compléments
en POO Encapsulation
Aldric Degorre Aliasing : exercice

Introduction
Lesquelles des classes A, B, C et D garantissent que l’entier contenu dans l’attribut d
Style
garde la valeur qu’on y a mise à la construction ou lors du dernier appel à setData ?
Objets et
classes
Objets et classes
Membres et contextes
Encapsulation
c l a s s Data { class C {
Types imbriqués
public int x ; p r i v a t e Data d ;
public Data ( i n t x ) { t h i s . x = x ; } p u b l i c void setData ( Data d ) {
Types et public D a t a c o p y ( ) { r e t u r n new D a t a ( x ) ; } this .d = d ;
polymorphisme } }
}
Héritage class A {
p r i v a t e f i n a l Data d ; class D {
Généricité p u b l i c A ( Data d ) { t h i s . d = d ; } p r i v a t e f i n a l Data d ;
Gestion des } p u b l i c B ( Data d ) { t h i s . d = d . copy ( ) ; }
erreurs et p u b l i c void useData ( ) {
exceptions class B { C l i e n t . use ( d ) ;
p r i v a t e f i n a l Data d ; }
Interfaces / / c o p i e d é f e n s i v e ( EJ3 I t e m 5 0 ) }
graphiques p u b l i c B ( Data d ) { t h i s . d = d . copy ( ) ; }
p u b l i c Data getData ( ) { r e t u r n d ; }
Concurrence }

Exemple
Revient à répondre à : les attributs de ces classes peuvent-ils avoir des alias extérieurs ?
Compléments
en POO Encapsulation
Aldric Degorre Aliasing : comment l’empêcher.

Introduction Aliasing souvent indésirable (mais pas toujours !) → il faut savoir l’empêcher. Pour cela :
Style

Objets et • Mettre les attributs sensibles en private et préférer les variables locales aux
classes
Objets et classes attributs quand c’est possible.
Membres et contextes
Encapsulation • Effectuer des copies défensives (EJ3 Item 50) :
Types imbriqués

Types et
• de tout sous-objet qu’on veut partager (retourné par un de vos getteurs, ou passé en
polymorphisme paramètre d’une méthode extérieure appelée depuis votre classe) ;
Héritage • de tout objet passé en argument à une de vos méthodes ou constructeurs qui va être
Généricité stocké dans un attribut 88 .
Gestion des
erreurs et class A {
exceptions
private Data data;
Interfaces public A(Data data) { this.data = data.copy(); }
graphiques
public void setData(Data data) { this.data = data.copy(); }
Concurrence public Data getData() { return data.copy(); }
public void foo() { X.bar(data.copy()); }
}

88. Pour une référence fournie par autrui, impossible de savoir si elle a été ou si elle sera partagée un jour.
Compléments
en POO Encapsulation
Aldric Degorre Copie défensive qu’est-ce que c’est et comment la réalise-t-on ?

Introduction

Style

Objets et
classes
Objets et classes
• Objectif : obtenir deux objets égaux au moment de la copie, mais dont les
Membres et contextes
Encapsulation
évolutions futures seront indépendantes.
• 2 cas, en fonction du genre de valeur à copier :
Types imbriqués

Types et
polymorphisme • Si type primitif ou immuable 89 , pas d’évolutions futures → une copie directe suffit.
Héritage • Si type mutable → on crée un nouvel objet dont les attributs contiennent des copies
Généricité
défensives des attributs de l’original (et ainsi de suite, récursivement).
Gestion des
erreurs et
exceptions

Interfaces
graphiques

Concurrence
89. Type immuable (immutable) : type (en fait toujours une classe) dont toutes les instances sont des
objets non modifiables.
C’est une propriété souvent recherchée, notamment en programmation concurrente.
Détails Contraire : mutable (mutable).
Compléments
en POO Encapsulation
Aldric Degorre Copie défensive qu’est-ce que c’est et comment la réalise-t-on ? (Exemple)

Introduction

Style

Objets et public class Item {


classes int productNumber; Point location; String name;
Objets et classes
Membres et contextes public Item copy() { // Item est mutable, donc cette méthode est utile
Encapsulation Item ret = new Item();
Types imbriqués
ret.productNumber = productNumber; // int est primitif, une copie simple suffit
Types et ret.location = new Point(location.x, location.y); // Point est mutable, il faut
polymorphisme
une copie profonde
Héritage ret.name = name; // String est immuable, une copie simple suffit
Généricité return ret;
}
Gestion des
erreurs et }
exceptions

Interfaces
graphiques Remarque : il est impossible de faire une copie défensive d’une classe mutable dont on
Concurrence n’est pas l’auteur si ses attributs sont privés et l’auteur n’a pas prévu lui-même la copie.

Exemple
Compléments
en POO Encapsulation
Aldric Degorre Immuabilité...

Introduction

Style ... ah et comment savoir si un type est immuable ? Nous y reviendrons.


Objets et
classes Pour l’instant on peut retenir que sont immuables :
Objets et classes
Membres et contextes
Encapsulation
• la classe String ;
Types imbriqués

Types et • toutes les primitive wrapper classes : Boolean, Char, Byte, Short, Integer,
polymorphisme
Long, Float et Double ;
Héritage

Généricité • d’autres sous-classes de Number : BigInteger et BigDecimal ;


Gestion des
erreurs et
• plus généralement, toute classe 90 dont la documentation dit qu’elle l’est.
exceptions

Interfaces En pratique, les 8 types primitfs (boolean, char, byte, short, int, long, float,
graphiques
double) se comportent aussi, fonctionnellement 91 , comme des types immuables 92 .
Concurrence

90. Oui, “classe”, car les types définis par les interfaces ne peuvent pas être garantis immuables.
91. Pour les autres aspects, tels que la performance en temps et en espace, le comportement est différent.
Détails 92. Mais la distinction mutable/immuable n’a pas de sens pour des valeurs directes.
Compléments
en POO Encapsulation
Aldric Degorre Aliasing : pourquoi ce phénomène peut nous gêner plus tard...

Introduction

Style

Objets et
classes
Objets et classes En cas d’alias extérieur :
Membres et contextes
Encapsulation
Types imbriqués • pas d’invariant prouvable, notamment, la classe ne peut pas être immuable (toute
Types et
polymorphisme instance peut être modifiée par un tiers) ;
Héritage • on ne peut empêcher les modifications concurrentes 93 de l’objet aliasé, dont le
Généricité
résultat est notoirement imprévisible. 94
Gestion des
erreurs et
exceptions

Interfaces
graphiques

Concurrence

93. Faisant intervenir un autre thread, cf. chapitre sur la programmation concurrente.
94. Ce problème se pose, plus généralement, dès lors qu’une référence manipulée dans une méthode
Supplément donnée possède des alias extérieurs.
Compléments
en POO Encapsulation
Aldric Degorre Aliasing : remarque sans rapport avec l’encapsulation

Introduction

Style

Objets et
classes
Objets et classes
L’impossibilité d’alias extérieur au frame 95 d’une méthode est aussi intéressante, car elle
Membres et contextes
Encapsulation
autorise la JVM à optimiser en allouant l’objet directement en pile plutôt que dans le tas.
Types imbriqués

Types et En effet : comme l’objet n’est pas référencé en dehors de l’appel courant, il peut être
polymorphisme
détruit sans risque au retour de la méthode.
Héritage

Généricité La recherche de la possilité qu’une exécution crée des alias externes (à une classe ou
Gestion des une méthode) s’appelle l’escape analysis 96 .
erreurs et
exceptions

Interfaces
graphiques

Concurrence

95. frame = zone de mémoire dans la pile, dédiée au stockage des informations locales pour un appel de
méthode donné.
Supplément 96. Traduction proposée : analyse d’échappement ?
Compléments
en POO Types imbriqués
Aldric Degorre Qu’est-ce que c’est ?

Introduction

Style Java permet de définir un type (classe ou interface) imbriqué 97 à l’intérieur d’une autre
Objets et
classes
définition de type (dit englobant 98 ) :
Objets et classes
Membres et contextes public class ClasseStupide {
Encapsulation
public static interface IToto {
Types imbriqués
public static class CTata {
Types et public enum EPlop {
polymorphisme
VTOTO;
Héritage public interface JToto { }
Généricité }
Gestion des
}
erreurs et }
exceptions }
Interfaces
graphiques

Concurrence
97. La plupart des documentations ne parlent en réalité que de “classes imbriquées” (nested classes), mais
c’est trop réducteur. D’autres disent “classes internes”/inner classes, mais ce nom est réservé à un cas
particulier. Voir la suite.
98. enclosing... mais on voit aussi outer/externe
Compléments
en POO Types imbriqués
Aldric Degorre Pour quoi faire ?

Introduction L’imbrication permet l’encapsulation des définitions de type et de leur contenu :


Style
class A {
Objets et static class AA { static int x; } // définition de x à l'intérieur de AA
classes
Objets et classes
private static class AB { } // comme pour tout membre, la visibilité peut être
Membres et contextes modifiée (ici private, mais public et protected sont aussi possibles)
Encapsulation
Types imbriqués
void fa() {
Types et
// System.out.println(x); // non, x n'est pas défini ici ! <- pas de pollution
polymorphisme
de l'espace de nom du type englobant par les membres du type imbriqué
Héritage System.out.println(AA.x); // oui !
Généricité }
Gestion des
erreurs et }
exceptions

Interfaces class B {
graphiques void fb() {
Concurrence // new AA(); // non ! -> classe imbriquée pas dans l'espace de noms du package
new A.AA(); // <- oui !
// new A.AB(); <- non ! (AB est private dans A)
}
Analyse }
Compléments
en POO Types imbriqués
Aldric Degorre Pour quoi faire ?

Introduction
• définitions du contexte englobant incluses dans contexte imbriqué (sans chemin).
Style

Objets et
• Type englobant et types membres peuvent accéder aux membres private des uns
classes
Objets et classes
des autres → utile pour partage de définitions privées entre classes “amies” 99 .
Membres et contextes

L’exemple ci-dessous compile :


Encapsulation
Types imbriqués

Types et
polymorphisme
class TE {
static class TIA {
Héritage
private static void fIA() { fE(); } // pas besoin de donner le chemin de fE
Généricité }
Gestion des
erreurs et static class TIB {
exceptions private static void fIB() { }
Interfaces }
graphiques

Concurrence private static void fE() { TIB.fIB(); } // TIB.fIB visible malgré private
}

99. La notion de classe imbriquée peut effectivement, en outre, satisfaire le même besoin que la notion de
Analyse friend class en C++ (quoique de façon plus grossière... ).
Compléments
en POO Plusieurs sortes de types imbriqués
Aldric Degorre Définitions et classification

Introduction

Style
Classification des types imbriqués/nested types 100
Objets et
classes • types membres statiques/static member classes 101 : types définis directement
Objets et classes
Membres et contextes dans la classe englobante, définition précédée de static
Encapsulation
Types imbriqués
• classes internes/inner classes : les autres types imbriqués (toujours des classes)
Types et
polymorphisme • classes membres non statiques/non-static member classes 102 : définies directement
Héritage
dans le type englobant
Généricité • classes locales/local classes : définies dans une méthode avec la syntaxe habituelle
Gestion des
erreurs et
(class NomClasseLocale { /*contenu */})
exceptions • classes anonymes/anonymous classes : définies “à la volée” à l’intérieur d’une
Interfaces expression, afin d’instancier un objet unique de cette classe :
graphiques
new NomSuperTypeDirect(){ /*contenu */}.
Concurrence

100. J’essaye de suivre la terminologie de la JLS... traduite, puis complétée par la logique et le bon sens.
101. La JLS les appelle static nested classes... oubliant que les interfaces membres existent aussi !
102. parfois appelées juste inner classes ; pas de nom particulier donné dans la JLS.
Compléments
en POO Types imbriqués
Aldric Degorre Exemple de type membre statique

Introduction La définition de classe prend la place d’une déclaration de membre du type englobant et
Style
est précédée de static.
Objets et
classes class MaListe<T> implements List<T> {
Objets et classes
Membres et contextes
private static class MonIterateur<U> implements Iterator<U> {
Encapsulation // ces méthodes travaillent sur les attributs de listeBase
Types imbriqués
private final MaListe listeBase;
Types et public MonIterateur(MaListe l) { listeBase = l; }
polymorphisme public boolean hasNext() {...}
Héritage public U next() {...}
public void remove() {...}
Généricité
}
Gestion des
erreurs et
exceptions ...
Interfaces
graphiques public Iterator<T> iterator() { return new MonIterateur<T>(this); }
}
Concurrence

On peut créer une instance de MonIterateur depuis n’importe quel contexte (même
Exemple statique) dans MaListe avec juste “new MonIterateur<_>(_)”.
Compléments
en POO Types imbriqués
Aldric Degorre Exemple de classe membre non statique

Introduction Définition similaire au cas précédent, mais sans le mot-clé static.


Style
class MaListe<T> implements List<T> {
Objets et private class MonIterateur implements Iterator<T> {
classes
Objets et classes
// ces méthodes utilisent les attributs non statiques de MaListe directement
Membres et contextes public boolean hasNext() {...}
Encapsulation
Types imbriqués
public T next() {...}
public void remove() {...}
Types et
polymorphisme
}
...
Héritage
public Iterator<T> iterator() {
Généricité return new MonIterateur(); // possible parce que iterator() n'est pas statique
Gestion des }
erreurs et }
exceptions

Interfaces
graphiques • Pour créer une instance de MaListe<String>.MonIterateur, il faut évaluer
Concurrence
“new MonIterateur()” dans le contexte d’une instance de MaListe<String>.
• Si on n’est pas dans le contexte d’une telle instance, on peut écrire
Exemple “x.new MonIterateur()” (où x instance de MaListe<String>).
Compléments
en POO Types imbriqués
Aldric Degorre Accès à l’instance imbriquée et à l’instance englobante : this et Outer.this

Introduction Soit TI un type imbriqué dans TE, type englobant. Alors, dans TI :
Style

Objets et
• this désigne toujours (quand elle existe) l’instance courante de TI ;
classes
Objets et classes
• TE.this désigne toujours (quand elle existe ) l’instance englobante, c.-à-d.
Membres et contextes
Encapsulation l’instance courante de TE, c.-à-d. :
Types imbriqués
• si TI classe membre non statique, la valeur de this dans le contexte où l’instance
Types et
polymorphisme courante de TI a été créée. Exemple :
Héritage class CE {
Généricité int x = 1;
class CI {
Gestion des
erreurs et int y = 2;
exceptions void f() { System.out.println(CE.this.x + "␣" + this.y); }
Interfaces }
graphiques }
Concurrence // alors new CE().new CI().f(); affichera "1 2"

• si TI classe locale, la valeur de this dans le bloc dans lequel TI a été déclarée.

La référence TE.this est en fait stockée dans toute instance de TI (attribut caché).
Compléments
en POO Types imbriqués
Aldric Degorre Exemple de classe locale

Introduction

Style
La définition de classe se place comme une instruction dans un bloc (gén. une méthode) :
Objets et
classes class MaListe<T> implements List<T> {
Objets et classes
Membres et contextes
...
Encapsulation public Iterator<T> iterator() {
Types imbriqués
class MonIterateur implements Iterator<T> {
Types et public boolean hasNext() {...}
polymorphisme public T next() {...}
Héritage public void remove() {...}
}
Généricité
return new MonIterateur()
Gestion des }
erreurs et
exceptions }
Interfaces
graphiques
En plus des membres du type englobant, accès aux autres déclarations du bloc
Concurrence
(notamment variables locales 103 ).

Exemple 103. Oui, mais seulement si effectivement finales... : si elles ne sont jamais ré-affectées.
Compléments
en POO Types imbriqués
Aldric Degorre Exemple de classe anonyme

Introduction

Style

Objets et
classes La définition de classe est une expression dont la valeur est une instance 104 de la classe.
Objets et classes
Membres et contextes
Encapsulation
class MaListe<T> implements List<T> {
Types imbriqués ...
Types et public Iterator<T> iterator() {
polymorphisme return /* de là */ new Iterator<T>() {
Héritage
public boolean hasNext() {...}
public T next() {...}
Généricité
public void remove() {...}
Gestion des } /* à là */ ;
erreurs et }
exceptions
}
Interfaces
graphiques

Concurrence

Exemple 104. La seule instance.


Compléments
en POO Classes anonymes
Aldric Degorre
Classe anonyme =
Introduction

Style • cas particulier de classe locale avec syntaxe allégée


Objets et
classes
→ comme classes locales, accès aux déclarations du bloc 105 ;
• déclaration “en ligne” : c’est syntaxiquement une expression, qui s’évalue comme
Objets et classes
Membres et contextes

une instance de la classe déclarée ;


Encapsulation
Types imbriqués

Types et
polymorphisme • déclaration de classe sans donner de nom =⇒ instanciable une seule fois
Héritage → c’est une classe singleton ;
Généricité
• autre restriction : un seul supertype direct 106 (dans l’exemple : Iterator).
Gestion des
erreurs et
exceptions Question : comment exécuter des instructions à l’initialisation d’une classe anonyme alors qu’il n’y a pas de constructeur ?
Interfaces → Réponse : utiliser un “bloc d’initialisation” ! (Au besoin, cherchez ce que c’est.)
graphiques

Concurrence Syntaxe encore plus concise : lambda-expressions (cf. chapitre dédié), par ex.
x -> System.out.println(x).
105. Avec la même restriction : variables locales effectivement finales seulement.
106. Une classe peut généralement, sauf dans ce cas, implémenter de multiples interfaces.
Compléments
en POO Types imbriqués
Aldric Degorre Accessibilité du contexte englobant

Introduction class/interface/enum TypeEnglobant {


Style static int x = 4;
Objets et
static class/interface/enum TypeMembre { static int y = x; }
classes static int z = TypeMembre.y;
Objets et classes }
Membres et contextes
Encapsulation
Types imbriqués

Types et
Le contexte interne du type imbriqué contient toutes les définitions du contexte externe.
polymorphisme
Ainsi, sont accessibles directement (sans chemin 107 ) :
Héritage

Généricité • dans tous les cas : les membres statiques du type englobant ;
Gestion des
erreurs et • pour les classes membres non statiques et classes locales dans bloc non statique :
exceptions
tous les membres non statiques du type englobant ;
Interfaces
graphiques
• pour les classes locales : les définitions locales 108 .
Concurrence

Réciproque fausse : depuis l’extérieur de TI, accès au membre y de TI en écrivant TI.y.


107. sauf s’il faut lever une ambiguïté
108. seulement effectivement finales pour les variables...
Compléments
en POO Plusieurs sortes de types imbriqués
Aldric Degorre D’accord, mais lequel choisir ? 111

Introduction
Utilisé (nommé) dans plusieurs méthodes ?
Style

Objets et non
classes oui
Objets et classes
Membres et contextes Instancié plusieurs fois
Utilisé dans plusieurs types
Encapsulation
ou bien
Types imbriqués
niveau package 109 ?
Types et
besoin de plusieurs types parent ?
polymorphisme no
i n
ou

no
i
Héritage

ou

n
Dépend d’une
Généricité type niveau package classe locale classe anonyme
Gestion des
instance englobante ? 110
erreurs et
exceptions
n ou
Interfaces no i
graphiques
type membre statique classe membre non statique
Concurrence

109. C’est à dire non imbriqués, définis directement dans les fichiers .java.
110. Ce sont en fait les instances de la classe interne qui contiennent une référence vers l’objet englobant.
Résumé 111. Cf. Effective Java 3rd edition, Item 24 : Favor static member classes over nonstatic.
Compléments
en POO Types imbriqués
Aldric Degorre Remarques et limitations diverses

Introduction
• Dans une interface englobante, les types membres sont 112 public et static.
Style

Objets et
• Dans les classes locales (et anonymes), on peut utiliser les variables locales du
classes
Objets et classes
bloc seulement si elles sont effectivement finales (c.à.d. déclarées final, ou bien
Membres et contextes
Encapsulation
jamais modifiées).
Types imbriqués Explication : l’instance de la classe locale peut “survivre” à l’exécution du bloc. Donc elle doit contenir une copie
Types et des variables locales utilisées. Or les 2 copies doivent rester cohérentes → modifications interdites.
polymorphisme
Une alternative non retenue : stocker les variables partagées dans des objets dans le tas, dont les références
Héritage

Généricité
seraient dans la pile. On pourrait aisément programmer ce genre de comportement au besoin.
Gestion des • Les classes internes 113 ne peuvent pas contenir de membres statiques (à part
erreurs et
exceptions attributs final).
Interfaces La raison est le décalage entre ce qu’est censé être une classe interne (prétendue dépendance à un certain
graphiques contexte dynamique) et son implémentation (classe statique toute bête : ce sont en réalité les instances de la
Concurrence classe interne qui contiennent une référence vers, par exemple, l’instance englobante).
Une méthode statique ne pourrait donc pas accéder à ce contexte dynamique, rompant l’illusion recherchée.
112. Nécessairement et implicitement.
Détails 113. Tous les types imbriqués sauf les classes membres statiques
Compléments
en POO Types de données
Aldric Degorre

Introduction

Style

Objets et
classes

Types et
polymorphisme
• type de données = ensemble d’éléments représentant des données de forme
Le système de types
Sous-typage
similaire, traitables de la même façon par un même programme.
Transtypage
Polymorphisme(s) • Chaque langage de programmation a sa propre idée de ce qu’il faut typer, de quand
Surcharge
Interfaces
il faut typer, de quels types il faut distinguer et comment, etc.
Héritage
On parle de différents systèmes de types.
Généricité

Gestion des
erreurs et
exceptions

Interfaces
graphiques

Concurrence

Révision
Compléments
en POO Système de types de Java
Aldric Degorre Caractéristiques principales

Introduction

Style
• typage qualifié de “fort” (mais ça ne veut pas dire grand chose 114 )
Objets et
classes
• typage statique : le compilateur vérifie le type des expressions du code source
Types et
polymorphisme
• typage dynamique : à l’exécution, les objets connaissent leur type. Il est testable à
Le système de types
Sous-typage l’exécution (permet traitement différencié 115 dans code polymorphe).
Transtypage
Polymorphisme(s)
Surcharge
• sous-typage, permettant le polymorphisme : une méthode déclarée pour argument
Interfaces
de type T est appelable sur argument pris dans tout sous-type de T.
Héritage

Généricité
• 2 “sortes” de type : types primitifs (valeurs directes) et types référence (objets)
Gestion des
erreurs et
• typage nominatif 116 : 2 types sont égaux ssi ils ont le même nom. En particulier, si
exceptions
class A { int x; } et class B { int x; } alors A x = new B(); ne
Interfaces
graphiques passe pas la compilation bien que A et B aient la même structure.
Concurrence
114. Concept en réalité flou. En outre, Java fait des conversions de type implicites (= faiblesse).
115. Via la liaison tardive/dynamique et via mécanismes explicites : instanceof et réflexion.
Résumé 116. Contraire : typage structurel (ce qui compte est la structure interne du type, pas le nom donné)
Compléments
en POO Types de données en Java
Aldric Degorre Deux catégories

Introduction

Style Pour des raisons liées à la mémoire et au polymorphisme, 2 catégories 117 de types :
Objets et
classes types primitifs types référence
Types et
polymorphisme
données représentées données simples données complexes (objets )
Le système de types
Sous-typage
valeur 118 d’une instance donnée directement adresse d’un objet ou null
Transtypage
Polymorphisme(s)
espace occupé 32 ou 64 bits 32 bits
Surcharge
Interfaces
nombre de types 8 (fixé, cf. nombreux fournis dans le JDK
Héritage page suivante) et on peut en programmer
Généricité casse du nom minuscules majuscule initiale (par convention)
Gestion des
erreurs et Il existe quelques autres différences, abordées dans ce cours.
exceptions

Interfaces 117. Les distinctions primitif/objet et valeur directe/référence coïncident en Java, mais c’est juste en Java.
graphiques
Ex : C++ possède à la fois des objets valeur directe et des objets accessibles par pointeur !
Concurrence
En Java actuel, on peut donc remplacer “type référence” par “type objet” et “type primitif” par “type à valeur
directe” sans changer le sens d’une phrase... mais ça pourrait changer (projet Valhalla) !
Révision 118. Les 32 bits stockés dans à l’adresse d’une variable ou bien empilés après évaluation d’une expression.
Compléments
en POO Types de données en Java
Aldric Degorre Zoom sur les types primitifs

Introduction

Style
Les 8 types primitifs :
Objets et
Nom description t. contenu t. utilisée exemples
classes byte entier très court 8 bits 1 mot 119 127,-19
Types et
polymorphisme short entier court 16 bits 1 mot -32_768 , 15_903
Le système de types
Sous-typage
int entier normal 32 bits 1 mot 23_411_431
Transtypage
Polymorphisme(s)
long entier long 64 bits 2 mots 3_411_431_434L
Surcharge
Interfaces
float réel à virgule flottante 32 bits 1 mot 3_214.991f
Héritage double idem, double précision 64 bits 2 mots -223.12 , 4.324E12
Généricité char caractère unicode 16 bits 1 mot 'a' '@' '\0'
Gestion des boolean valeur de vérité 1 bit 1 mot true, false
erreurs et
exceptions
Cette liste est exhaustive : le programmeur ne peut pas définir de types primitifs.
Interfaces
graphiques
Chaque type primitif a un nom commençant par une minuscule, qui est un mot-clé
Concurrence
réservé de Java (String int = "truc" ne compile pas).
Révision 119. 1 mot = 32 bits
Compléments
en POO Zones de la mémoire
Aldric Degorre La pile/stack (1)

Introduction

Style

Objets et La mémoire de la JVM comporte plusieurs zones (notamment la pile et le tas).


classes

Types et Pile :
polymorphisme
Le système de types
Sous-typage
• les opérations courantes prennent/retirent leurs opérandes de la pile et écrivent
leurs résultats dans cette même pile (ordre LIFO) ;
Transtypage
Polymorphisme(s)
Surcharge
Interfaces
• lors de l’appel d’une méthode, ses paramètres effectifs sont empilés ; lors de son
Héritage
retour, toute la mémoire qui a servi à son exécution (paramètres, variables locales,
Généricité
résultats intermédiaires) est libérée/dépilée, et le résultat empilé ;
Gestion des
erreurs et
exceptions • dans la pile, on ne stocke que des blocs d’1 ou 2 mots → on ne peut stocker que
Interfaces des valeurs primitives et des (pseudo-)adresses d’objets. 120
graphiques

Concurrence

120. Tout se passe comme si c’était vrai. En fait, la JVM peut optimiser en mettant les objets locaux en pile.
Compléments
en POO Zones de la mémoire
Aldric Degorre La pile/stack (2)

Introduction

Style

Objets et
classes
En réalité, quelques “complications” :
Types et
polymorphisme
Le système de types
Sous-typage
• 2 niveaux de pile : la pile de premier niveau contient seulement des frames. À
Transtypage
Polymorphisme(s)
l’appel d’une méthode on empile un nouveau frame qui est dépilé à son retour.
Un frame contient une zone pour les variables locales de l’appel en cours ainsi
Surcharge
Interfaces

Héritage qu’une pile (de deuxième niveau) pour l’évaluation des expressions.
Généricité
Pour une méthode donnée, un frame a une taille fixe déterminée à la compilation.
Gestion des
erreurs et • Autre “complication” : il y a en fait une pile par thread.
exceptions

Interfaces
graphiques

Concurrence

Supplément
Compléments
en POO Zones de la mémoire
Aldric Degorre Le tas/heap et autres zones

Introduction Tas :
Style

Objets et • Objets (tailles diverses) stockés dans le tas.


classes

Types et
• Tas géré de façon automatique : quand on crée un objet, l’espace est réservé
polymorphisme
Le système de types
automatiquement et quand on ne l’utilise plus, la JVM le détecte et libère l’espace
Sous-typage
Transtypage
(ramasse-miettes/garbage-collector).
• L’intérieur de la zone réservée à un objet ne contient elle-même que des valeurs
Polymorphisme(s)
Surcharge

primitives et des adresses d’autres objets.


Interfaces

Héritage

Généricité
Autres zones de mémoire dans la JVM :
Gestion des
erreurs et
exceptions • zone des méthodes : classes, dont méthodes (code octet) et attributs statiques
Interfaces
graphiques
• zone des registres 121 , contient notamment les registres suivants :
Concurrence • l’adresse de la prochaine instruction à exécuter
• l’adresse du sommet de la pile
121. Comme pour la pile, il y a en fait une zone de registres par thread.
Compléments
en POO Références et valeurs directes
Aldric Degorre Interprétations

Introduction Valeurs calculées stockées, en pile ou dans un champ, sur 1 mot (2 si long ou double)
Style

Objets et
• types primitifs : directement la valeur intéressante
classes
• types références : une adresse mémoire (pointant vers un objet dans le tas).
Types et
polymorphisme
Le système de types 2 interprétations :
Sous-typage
Transtypage
Polymorphisme(s)
• Bas niveau : expressions de type valeur ou référence sont la même chose (une suite
Surcharge
Interfaces
de 32 bits... peu importe ce qu’ils représentent).
Héritage • Haut niveau : les expressions de type référence “représentent” l’objet dans le tas,
Généricité pas son adresse. L’adresse mémoire n’est qu’un intermédiaire technique.
Gestion des
erreurs et
exceptions
Pour Java, le haut niveau est privilégié : le compilateur tient compte du type d’une
Interfaces
référence pour dire si elle a le droit de référencer un objet de tel ou tel type.
graphiques

Concurrence
Exemple : une variable de type String et une de type Point2D contiennent tous deux le même genre de données : un mot
représentant une adresse mémoire. Pourtant la première pointera toujours sur une chaîne de caractères alors que la
Discussion seconde pointera toujours sur la représentation d’un point du plan.
Compléments
en POO Références et valeurs directes
Aldric Degorre Conséquences sur l’affectation et l’égalité

Introduction

Style

Objets et
classes La distinction référence/valeur directe a plusieurs conséquences à l’exécution.
Types et
polymorphisme Pour x et y variables de types références :
Le système de types
Sous-typage
Transtypage • Après l’affectation x = y, les deux variables désignent le même emplacement
mémoire (aliasing).
Polymorphisme(s)
Surcharge
Interfaces

Héritage
Si ensuite on exécute l’affectation x.a = 12, alors après y.a vaudra aussi 12.
Généricité • Si les variables x et z désignent des emplacements différents, le test d’identité 122
Gestion des
erreurs et
x == z vaut false, même si le contenu des deux objets référencés est identique.
exceptions

Interfaces
graphiques

Concurrence

122. Pour les primitifs, identité et égalité sont la même chose. Pour les objets, le test d’égalité est la
Révision méthode public boolean equals(Object other).
Compléments
en POO Références et valeurs directes
Aldric Degorre Conséquence sur le passage de paramètres à l’appel d’une méthode

Introduction

Style
Rappel : En Java, quand on appelle une méthode, on passe les paramètres par valeur
Objets et
classes uniquement : une copie de la valeur du paramètre est empilée avant appel.
Types et
polymorphisme Ainsi :
Le système de types
Sous-typage
Transtypage
• pour les types primitifs 123 → la méthode travaille sur une copie des données
Polymorphisme(s)
Surcharge
Interfaces
• pour les types référence → c’est l’adresse qui est copiée ; la méthode travaille avec
Héritage cette copie, qui pointe sur... les mêmes données que l’adresse originale.
Généricité

Gestion des
Conséquence :
erreurs et
exceptions • Modif. sur donnée de type primitif passée en paramètre → disparait
Interfaces
graphiques • Modif. sur donnée de type référence passée en paramètre → persiste
Concurrence

Révision 123. = types à valeur directe, pas les types référence


Compléments
en POO Références et valeurs directes
Aldric Degorre Conséquence sur le passage de paramètres à l’appel d’une méthode (remarques)

Introduction

Style
• Pour les objets immuables (c.-à-d. non modifiables), on retrouve le comportement
Objets et
classes des valeurs primitives : si on passe une variable de type objet immuable à une
Types et
polymorphisme
méthode, on sait qu’aucune “modification” ne sera gardée.
• On entend souvent “En Java, les objets sont passés par référence”.
Le système de types
Sous-typage
Transtypage
Polymorphisme(s)
Surcharge
Ce n’est pas rigoureux !
Interfaces
Le passage par référence désigne généralement autre chose, de très spécifique 124
Héritage
(notamment en C++, PHP, Visual Basic .NET, C#, REALbasic...).
Généricité

Gestion des
erreurs et
exceptions 124. Passage par référence : quand on passe une variable v (plus généralement, une lvalue) en paramètre, le
Interfaces paramètre formel (à l’intérieur de la méthode) est un alias de v (un pointeur “déguisé” vers l’adresse de v,
graphiques
mais utilisable comme si c’était v).
Concurrence
Toute modification de l’alias modifie la valeur de v.
En outre, le pointeur sous-jacent peut pointer vers la pile (si v variable locale), ce qui n’est jamais le cas des
Discussion “références” de Java.
Compléments
en POO Typages dynamique et statique
Aldric Degorre Quand vérifier la cohérence des données ?

Introduction

Style • Pour s’assurer qu’un programme fonctionne bien, des vérifications de type peuvent
Objets et
classes
avoir lieu à différents stades. 125
Types et • Pour certains langages (Python, PHP, Javascript, ...), la vérification ne se fait qu’à
polymorphisme
Le système de types
Sous-typage
l’exécution (typage dynamique uniquement).
• Pour d’autres langages (C, C++, OCaml, ...), l’essentiel des vérifications a lieu dès la
Transtypage
Polymorphisme(s)

compilation (typage statique).


Surcharge
Interfaces

Héritage
• Les “entités” qui reçoivent un type dépendent en fait du moment où les vérifications
Généricité

Gestion des
de type s’opèrent.
erreurs et
exceptions
Typage statique → on type les expressions du programme
Interfaces Typage dynamique → on type les données existant à l’exécution.
graphiques

Concurrence
Où se Java se situe-t-il ? Que type-t-on en Java ?
125. ou bien jamais, pour les langages les plus “bas niveau” (assembleur x86, p. ex.)
Compléments
en POO Stades de vérification et entités typables en Java
Aldric Degorre À la compilation : les expressions

Introduction Java → langage à typage statique, mais avec certaines vérifications à l’exécution 126 :
Style

Objets et • À la compilation on vérifie le type des expressions 127 (analyse statique).


classes

Types et
Type déterminé via les annotations de type explicites et par déduction. 128
polymorphisme • Le compilateur sait que l’expression "bonjour" est de type String. (idem pour les
Le système de types
Sous-typage types valeur : 42 est toujours de type int).
Transtypage
Polymorphisme(s)
• Si on déclare Scanner s, alors l’expression s est reconnue comme de type
Surcharge
Interfaces
Scanner.
Héritage
• Le compilateur sait aussi déterminer que 1.0 + 2 est de type double.
Généricité La compatibilité de type de chaque expression avec son contexte est vérifiée.
Gestion des • Le compilateur voit que int x = 1; System.out.println(x/2); est bien typé.
erreurs et
exceptions • Mais il voit aussi que Math.cos("bonjour") est mal typé.
Interfaces 126. C’est en fait une caractéristique habituelle des langages à typage essentiellement mais autorisant le
graphiques
polymorphisme par sous-typage.
Concurrence
127. morceaux de texte évaluables du programme
128. Java ne dispose pas de système d’inférence aussi complet que OCaml, par exemple, néanmoins cela
n’empêche pas de nombreuses déductions directes comme dans les exemples donnés ici.
Compléments
en POO Stades de vérification et entités typables en Java
Aldric Degorre À l’exécution : les objets

Introduction

Style • À l’exécution, la JVM peut vérifier le type des objets 129 .


Objets et À cette fin, lors de l’instanciation d’un objet, le nom de sa classe (= son type exact),
classes
y est inscrit (définitivement). Ceci permet :
Types et
polymorphisme • d’exécuter des tests demandés par le programmeur (comme instanceof) ;
• à la méthode getClass() de retourner un résultat ;
Le système de types
Sous-typage
Transtypage
Polymorphisme(s)
• de faire fonctionner la liaison dynamique (dans x.f(), la JVM regarde le type de
Surcharge
Interfaces
l’objet référencé par x avant de savoir quel f() exécuter) ;
Héritage
• de vérifier la cohérence de certaines conversions de type :
Généricité
Object o; ... ; String s = (String)o;
Gestion des
erreurs et
• Ceci ne concerne pas les valeurs primitives/directes : pas de place pour coder le
exceptions
type dans les 32 bits de la valeur directe ! (et c’est en fait inutile)
Interfaces
graphiques

Concurrence La différence entre objets et valeurs directes provient du traitement différent du


polymorphisme et des conversions de type (casts, voir plus loin).
129. Ces entités n’existent pas avant l’exécution, de toute façon !
Compléments
en POO Relation entre type statique et type dynamique
Aldric Degorre
Pour une variable ou expression :
Introduction

Style
• son type statique est son type tel que déduit par le compilateur (pour une variable :
Objets et c’est le type de sa déclaration) ;
classes
• son type dynamique est la classe de l’objet référencé (par cette variable ou par le
Types et
polymorphisme résultat de l’évaluation de cette expression).
Le système de types
Sous-typage
Transtypage
• Le type dynamique ne peut pas être déduit à la compilation.
Polymorphisme(s)
Surcharge
• Le type dynamique change 130 131 au cours de l’exécution.
Interfaces

Héritage La vérification statique et les règles d’exécution garantissent la propriété suivante :


Généricité

Gestion des Le type dynamique d’une variable ou expression est


erreurs et
exceptions toujours un sous-type 132 de son type statique.
Interfaces
graphiques
130. Pour une variable : après chaque affectation, un objet différent peut être référencé.
Pour une expression : une expression peut être évaluée plusieurs fois lors d’une exécution du programme et
Concurrence
donc référencer, tour à tour, des objets différents.
131. Remarque : le type (la classe) d’un objet donné est, en revanche, fixé(e) dès son instanciation.
132. Nous expliquons le sous-typage juste après.
Compléments
en POO Notion de sous-typage
Aldric Degorre Définition abstraite

Introduction

Style

Objets et
classes

Types et
polymorphisme • Définition : le type A est sous-type de B (A <: B) (ou bien B supertype de A
(B :> A)) si toute entité 133 de type A
Le système de types
Sous-typage
Transtypage
Polymorphisme(s)
Surcharge
• est de type B
Interfaces
• (alt.) “peut remplacer” une entité de type B.
Héritage

Généricité
→ plusieurs interprétations possibles (mais contraintes par notre définition de “type”).
Gestion des
erreurs et
exceptions

Interfaces
graphiques

Concurrence

133. Pour Java, entité = soit expression, soit objet.


Compléments
en POO Notion de sous-typage
Aldric Degorre Interprétations

Introduction

Style

Objets et
classes

Types et
polymorphisme
Le système de types
• Interprétation faible : ensembliste. Tout sous-ensemble d’un type donné forme un
sous-type de celui-ci.
Sous-typage
Transtypage
Polymorphisme(s)
Surcharge Exemple : tout carré est un rectangle, donc le type carré est sous-type de rectangle.
Interfaces

Héritage

Généricité
→ insuffisant car un type n’est pas un simple ensemble : il est aussi muni d’opérations,
Gestion des
d’une structure, de contrats, ...
erreurs et
exceptions

Interfaces
graphiques

Concurrence

Analyse
Compléments
en POO Notion de sous-typage
Aldric Degorre Interprétations

Introduction

Style

Objets et
classes

Types et
polymorphisme
• Interprétation “minimale” : sous-typage structurel. A est sous-type de B si toute
Le système de types
Sous-typage
instance de A sait traiter les messages qu’une instance de B sait traiter.
Concrètement : A possède toutes les méthodes de B, avec des signatures au moins aussi
Transtypage
Polymorphisme(s)

permissives en entrée et au moins aussi restrictives en sortie. 134


Surcharge
Interfaces

Héritage

Généricité → sous-typage plus fort et utilisable car vérifiable en pratique, mais insuffisant pour
Gestion des prouver des propriétés sur un programme polymorphe.
erreurs et
exceptions

Interfaces
graphiques

Concurrence

Analyse 134. Contravariance des paramètres et covariance du type de retour.


Compléments
en POO Notion de sous-typage
Aldric Degorre Interprétations

Introduction

Style Pourquoi le sous-typage structurel est insuffisant ?


Objets et
classes Exemple :
Types et
polymorphisme
Le système de types • Dans le cours précédent, les instances de la classe FiboGen génèrent la suite de Fibonacci.
Sous-typage
Transtypage
• On peut prouver qu’un programme qui calcule la suite des quotients des valeurs générées par
Polymorphisme(s)

une instance directe de FiboGen sort des valeurs qui tendent vers le nombre d’or φ = 1+2 5 .
Surcharge
Interfaces

Héritage → on devrait pouvoir déclarer cette propriété comme contrat pour le type FiboGen.
Généricité
• Or rien empêche de créer un sous-type (sous-classe 135 ) de Fibogen dont la méthode next
Gestion des
erreurs et retournerait tout le temps 0. Le programme ne pourrait alors plus servir à calculer φ.
exceptions

Interfaces
→ le sous-type ne respecterait pas le contrat, alors que ses éléments sont instance de
graphiques FiboGen.
Concurrence

Analyse 135. Une sous-classe est bien un sous-type au sens structurel : les méthodes sont héritées.
Compléments
en POO Notion de sous-typage
Aldric Degorre Interprétations

Introduction

Style
• Interprétation idéale : Principe de Substitution de Liskov 136 (PSL). Un sous-type
Objets et
classes doit respecter tous les contrats du supertype, afin que ce qui est garanti pour un
Types et programme utilisant le supertype le soit aussi pour le sous-type.
polymorphisme
Le système de types
Sous-typage Exemple : les propriétés largeur et hauteur d’un rectangle sont modifiables indépendamment.
Transtypage
Polymorphisme(s)
Un carré ne satisfait pas ce contrat. Donc le type carré modifiable n’est pas sous-type de
Surcharge
Interfaces
rectangle modifiable.
Héritage En revanche, leurs versions non modifiables sont sous-types l’une de l’autre selon le PSL.
Généricité

Gestion des → Hélas, notion trop forte pour les compilateurs : un aucun programme ne sait vérifier
erreurs et
exceptions une telle substituabilité (indécidable).
Interfaces
graphiques Cette notion n’est pas implémentée par les compilateurs, mais c’est bien celle que le
Concurrence programmeur doit avoir en tête pour écrire des programmes corrects !
Analyse 136. C’est le “L” de la méthodologie SOLID.
Compléments
en POO Notion de sous-typage
Aldric Degorre Interprétations : en pratique

Introduction

Style
→ Interprétation en pratique : tout langage de programmation possède un système de
Objets et
classes règles simples et vérifiables par son compilateur, définissant “son” sous-typage.
Types et
polymorphisme
Le système de types Les grandes lignes du sous-typage selon Java : (détails dans JLS 4.10 et ci-après)
Sous-typage

• Pour les 8 types primitifs, il y a une relation de sous-typage pré-définie.


Transtypage
Polymorphisme(s)
Surcharge
Interfaces
• Pour les types référence, le sous-typage est nominal : A n’est sous-type de B que si
Héritage
A est déclaré comme tel (implements ou extends).
Généricité

Gestion des Mais la définition de A ne passe la compilation que si certaintes contraintes


erreurs et
exceptions
structurelles 137 sont vérifiées, concernant les redéfinitions de méthodes.
Interfaces
graphiques
• Types primitifs et types référence forment deux systèmes déconnectés. Aucun type
Concurrence référence n’est sous-type ou supertype d’un type primitif.

137. Cf. cours sur les interfaces et sur l’héritage pour voir quelles sont les contraintes exactes.
Compléments
en POO Notion de sous-typage pour Java
Aldric Degorre Relation de sous-typage en Java (1)

Introduction Types primitifs : (fermeture transitive et réflexive de la relation décrite ci-dessous)


Style

Objets et
• Un type primitif numérique est sous-type de tout type primitif numérique plus
classes
précis : byte <: short <: int <: long et float <: double.
Types et
polymorphisme • Par ailleurs long <: 138 float et char <: 139 int.
Le système de types
Sous-typage
Transtypage
• boolean est indépendant de tous les autres types primitifs.
Polymorphisme(s)
Surcharge
Interfaces

Héritage
double float long int short byte
Généricité

Gestion des
erreurs et
exceptions

Interfaces boolean char


graphiques

Concurrence
138. float (1 mot) n’est pas plus précis ou plus “large” que long (2 mots), mais il existe néanmoins une
conversion automatique du second vers le premier.
Résumé 139. Via la valeur unicode du caractère.
Compléments
en POO Notion de sous-typage pour Java
Aldric Degorre Relation de sous-typage en Java 143 (2)

Introduction

Style Types référence : (fermeture transitive et réflexive de la relation décrite ci-dessous)


Objets et
classes A <: B ssi une des règles suivantes s’applique : 140
Types et
polymorphisme • (implémentation d’interface) A est une classe, B une interface et A implémente B ;
Le système de types

• (héritage de classe) A et B sont des classes et A étend B ;


Sous-typage
Transtypage
Polymorphisme(s)
Surcharge
Interfaces
• (héritage d’interface) A et B sont des interfaces et A étend B ;
Héritage
• (comparaison à Object) B est Object 141 .
Généricité

Gestion des • (covariance des types tableau 142 ) A et B resp. de forme a[] et b[], avec a <: b ;
erreurs et
exceptions

Interfaces 140. Pour être exhaustif, il manque les règles de sous-typage pour les types génériques.
graphiques
141. C’est vrai même si A est une interface, alors même qu’aucune interface n’hérite de Object.
Concurrence
142. Les types tableaux sont des classes (très) particulières, implémentant les interfaces Cloneable et
Serializable. Donc tout type tableau est aussi sous-type de Object, Cloneable et Serializable.
Résumé 143. Cf. JLS 4.10.
Compléments
en POO Notion de sous-typage pour Java
Aldric Degorre Relation pour les types références, diagramme

Introduction Une partie du graphe de sous-typage : le type Integer et ses supertypes.


Style

Objets et
classes Object
Types et
polymorphisme
Le système de types
Sous-typage
Transtypage
Polymorphisme(s)
Surcharge
Serializable extends
Interfaces

Héritage
implements
Généricité

Gestion des
erreurs et Number Comparable<? super Integer>
exceptions

Interfaces
graphiques extends implements
Concurrence
Integer
Exemple
Compléments
en POO Notion de sous-typage pour Java
Aldric Degorre Principe fondamental

Introduction

Style

Objets et Principe fondamental


classes

Types et Dans un programme (sans surcharge 144 ) qui compile, remplacer une expression de type
polymorphisme
Le système de types A par une de type B 145 , avec B<: A, n’empêche pas le programme de compiler encore.
Sous-typage
Transtypage
Polymorphisme(s)
Surcharge Remarque : seule la compilation est garantie, ainsi que le fait que le résultat de la
Interfaces

Héritage
compilation est exécutable.
Généricité La correction du programme résultant n’est pas garantie !
Gestion des
erreurs et
exceptions
(normal : sous-typage Java plus faible que LSP)
Interfaces
graphiques

Concurrence
144. En effet, le type statique des arguments d’une méthode surchargée influe sur la résolution de la
surcharge et peut créer des ambiguïtés. Cf. chapitre sur le sujet.
Résumé 145. Syntaxiquement correcte ; avec identifiants tous définis dans leur contexte ; et bien typée.
Compléments
en POO Notion de sous-typage pour Java
Aldric Degorre Principe fondamental – explications

Introduction

Style

Objets et
classes Pourquoi ce remplacement ne gène pas l’exécution :
Types et
polymorphisme
Le système de types
• les objets sont utilisables sans modification comme instances du supertype 146
Sous-typage
Transtypage
(sous-typage inclusif). P. ex. : Object o = "toto" fonctionne.
Polymorphisme(s)
Surcharge • les valeurs primitives sont parfois interprétables sans modification, mais sont
Interfaces

Héritage
parfois modifiées vers la valeur la plus proche dans le supertype (sous-typage
Généricité coercitif). P. ex. : après l’affectation float f = 1_000_000_000_123L;, la
Gestion des variable f vaut 1.0E12 (on a perdu les derniers chiffres).
erreurs et
exceptions

Interfaces
graphiques

Concurrence

146. Les contraintes d’implémentation d’interface et d’héritage garantissent que les méthodes du supertype
peuvent être appelées
Compléments
en POO Notion de sous-typage pour Java
Aldric Degorre

Introduction

Style

Objets et Corollaires :
classes

Types et • on peut affecter à toute variable une expression de son sous-type ; (ex :
polymorphisme
Le système de types double z = 12;) ;
Sous-typage
Transtypage
Polymorphisme(s) • on peut appeler toute méthode avec des arguments d’un sous-type des types
déclarés dans sa signature (ex : Math.pow(3, 'z')) ;
Surcharge
Interfaces

Héritage
• on peut appeler toute méthode d’une classe T donné sur un récepteur instance
Généricité

Gestion des
d’une sous-classe de T (ex : "toto".hashCode()).
erreurs et
exceptions Ainsi, le sous-typage est la base du système de polymorphisme de Java.
Interfaces
graphiques

Concurrence
Compléments
en POO Transtypage (type casting)
Aldric Degorre

Introduction Transtypage = type casting = conversion de type d’une expression.


Style
Plusieurs mécanismes 147 148 :
Objets et
classes

Types et
• upcasting : d’un type vers un supertype (ex : Double vers Number)
polymorphisme
Le système de types
Sous-typage
• downcasting : d’un type vers un sous-type (ex : Object vers String)
Transtypage
Polymorphisme(s)
Surcharge
• boxing : d’un type primitif vers sa version “emballée” (ex : int vers Integer)
Interfaces

Héritage
• unboxing : d’un type emballé vers le type primitif correspondant (ex : Boolean vers
Généricité boolean)
Gestion des
erreurs et
• conversion en String : de tout type vers String (implicite pour concaténation)
exceptions

Interfaces
• parfois combinaison implicite de plusieurs mécanismes.
graphiques

Concurrence
147. Détaillés dans la JLS, chapitre 5.
148. On ne mentionne pas les mécanismes explicites et évidents tels que l’utilisation de méthodes penant
Résumé du A et retournant du B. Si on va par là, tout type est convertible en tout autre type.
Compléments
en POO Transtypage (type casting)
Aldric Degorre

Introduction

Style

Objets et
classes

Types et Tous ces mécanismes sont des règles permettant vérifier, à la compilation, si une
polymorphisme
Le système de types
expression peut être placée là où elle l’est.
Sous-typage
Transtypage
Polymorphisme(s)
Néanmoins, conséquences possibles à l’exécution :
Surcharge
Interfaces
• Parfois vraie modification des données (types primitifs).
Héritage

Généricité • Parfois vérification de la classe d’un objet (downcasting de référence).


Gestion des
erreurs et
exceptions

Interfaces
graphiques

Concurrence
Compléments
en POO Transtypage (type casting)
Aldric Degorre Autres termes employés (notamment dans la JLS)

Introduction

Style

Objets et
classes

Types et
polymorphisme
• Élargissement/widening et rétrécissement/narrowing : dans la JLS (5.1),
Le système de types
Sous-typage
synonymes respectifs de upcasting et downcasting.
Inconvénient : le sens étymologique (= réécriture sur resp. + de bits ou - de bits), ne
Transtypage
Polymorphisme(s)

représente pas la réalité en Java (cf. la suite).


Surcharge
Interfaces

Héritage
• Promotion : synonyme de upcasting. Utilisé dans la JLS (5.6) seulement pour les
Généricité
conversions implicites des paramètres des opérateurs arithmétiques. 149
Gestion des
erreurs et
exceptions

Interfaces
graphiques

Concurrence

Discussion 149. Alors qu’on pourrait expliquer ce mécanisme de la même façon que la résolution de la surcharge.
Compléments
en POO Transtypage (type casting)
Aldric Degorre Autres termes employés (notamment dans la JLS)

Introduction

Style

Objets et
classes • Coercition : conversion implicite de données d’un type vers un autre.
Types et
polymorphisme
Cf. sous-typage coercitif : mode de sous-typage où un type est sous-type d’un autre
Le système de types
Sous-typage
s’il existe une fonction 150 de conversion et que le compilateur insère implicitement
Transtypage
Polymorphisme(s)
des instructions dans le code octet pour que cette fonction soit appliquée.
Surcharge
Interfaces
Inconvénient : incohérences entre définitions de coercition et sous-typage coercitif ;
Héritage • la coercition ne suppose pas l’application d’une fonction ;
Généricité • Java utilise des coercitions sans rapport avec le sous-typage (auto-boxing,
Gestion des auto-unboxing, conversion en chaîne, …).
erreurs et
exceptions
→ On ne prononcera plus élargissement, rétrécissement, promotion ou coercition !
Interfaces
graphiques

Concurrence

Discussion 150. Au sens mathématique du terme. Pas forcément une méthode.


Compléments
en POO Transtypage, cas 1 : upcasting
Aldric Degorre

• Cas d’application : on souhaite obtenir une expression d’un supertype à partir d’une
Introduction

Style
expression d’un sous-type.
Objets et • L’upcasting est en général implicite (pas de marque syntaxique).
classes

Types et Exemple :
polymorphisme
Le système de types double z = 3; // upcasting (implicite) de int vers double
Sous-typage
Transtypage
Polymorphisme(s)
Surcharge
• Utilité, polymorphisme par sous-typage : partout où une expression de type T est
Interfaces
autorisée, toute expression de type T' est aussi autorisée si T' <: T.
Héritage

Généricité
Exemple : si class B extends A {}, void f(A a) et B b, alors l’appel f(b)
Gestion des est accepté.
erreurs et
exceptions • L’upcasting implicite permet de faire du polymorphisme de façon transparente.
Interfaces
graphiques • On peut aussi demander explicitement l’upcasting, ex : (double)4
Concurrence
• L’upcasting explicite sert rarement, mais permet parfois de guider la résolution de la
Révision
surcharge : remarquez la différence entre 3/4 et ((double)3)/ 4.
Compléments
en POO Transtypage, cas 2 : downcasting
Aldric Degorre

Introduction Downcasting :
Style

Objets et
• Cas d’application : on veut écrire du code spécifique pour un sous-type de celui qui
classes
nous est fourni.
Types et
polymorphisme
Le système de types
• Dans ce cas, il faut demander une conversion explicite.
Sous-typage
Transtypage Exemple : int x = (int)143.32.
Polymorphisme(s)
Surcharge • Utilité :
Interfaces

Héritage • (pour les objets) dans un code polymorphe, généraliste, on peut vouloir écrire une
Généricité partie qui travaille seulement sur un certain sous-type, dans ce cas, on teste la classe
Gestion des de l’objet manipulé et on downcast l’expression qui le référence :
erreurs et
exceptions if (x instanceof String) { String xs = (String) x; ... ;}
Interfaces
graphiques • Pour les nombres primitifs, on peut souhaiter travailler sur des valeurs moins
Concurrence précises : int partieEntiere = (int)unReel;

Révision
Compléments
en POO Trantypage, cas 2 : downcasting
Aldric Degorre Avertissement

Introduction Le code ci-dessous est probablement symptôme d’une conception non orientée objet :
Style
// Anti-patron :
Objets et void g(T x) {
classes
if (x instanceof C1) { C1 y = (C1) x; f1(y); }
Types et else if (x instanceof C2) { C2 y = (C2) x; f2(y); }
polymorphisme
Le système de types
else { /* quoi en fait ? on génère une erreur ? */}
Sous-typage }
Transtypage
Polymorphisme(s)

Quand c’est possible, on préfère utiliser la liaison dynamique :


Surcharge
Interfaces

Héritage public interface I { void f(); }


Généricité void g(I x) { x.f(); }
Gestion des
erreurs et // puis dans d'autres fichiers (voire autres packages)
exceptions
public class C1 implements I { public void f() { f1(this); }}
Interfaces public class C2 implements I { public void f() { f2(this); }}
graphiques

Concurrence
Avantage : les types concrets manipulés ne sont jamais nommés dans g, qui pourra
Discussion donc fonctionner avec de nouvelles implémentations de I sans modification.
Compléments
en POO Transtypage, cas 3 : auto-boxing/unboxing
Aldric Degorre Types “emballés”

Introduction

Style

Objets et
classes
• Pour tout type primitif, il existe un type référence “emballé” ou “mis en boîte”
Types et
polymorphisme (wrapper type ou boxed type) équivalent : int↔Integer, double↔Double, ...
Le système de types
Sous-typage
Transtypage
• Attention, contrairement à leurs équivalents primitifs, les différents types
Polymorphisme(s)
Surcharge
emballés ne sont pas sous-types les uns des autres ! Ils ne peuvent donc pas être
Interfaces
transtypés de l’un dans l’autre.
Héritage

Généricité
Double d = new Integer(5); →
Gestion des
Double d = new Integer(5).doubleValue(); ou encore
erreurs et
exceptions
Double d = 0. + (new Integer(5)); 151
Interfaces
graphiques

Concurrence

151. Spoiler : cet exemple utilise des conversions automatiques. Voyez-vous lesquelles ?
Compléments
en POO Transtypage, cas 3 : auto-boxing/unboxing
Aldric Degorre Conversions automatiques, depuis Java 5

Introduction

Style • Partout où un type emballé est attendu, une expression de type valeur
Objets et correspondant sera acceptée : auto-boxing.
classes

Types et • Partout où un type valeur est attendu, sa version emballée sera acceptée :
polymorphisme
Le système de types auto-unboxing.
Sous-typage
Transtypage
Polymorphisme(s)
• Cette conversion automatique permet, dans de nombreux usages, de confondre un
Surcharge
Interfaces
type primitif et le type emballé correspondant.
Héritage

Généricité Exemple :
Gestion des
erreurs et • Integer x = 5; est équivalent de Integer x = Integer.valueOf(5); 152
exceptions

Interfaces • Réciproquement int x = new Integer(12); est équivalent de


graphiques
int x = (new Integer(12)).intValue();
Concurrence

152. La différence entre Integer.valueOf(5) et new Integer(5) c’est que la fabrique statique
valueOf() réutilise les instances déjà créées, pour les petites valeurs (mise en cache).
Compléments
en POO Transtypage : à propos du “cast”
Aldric Degorre i.e., la notation (Type)expr

Introduction
• Particulièrement utile pour le downcasting, mais sert à toute conversion.
Style

Objets et
• Soient A et B des types et e une expression de type A, alors l’expression “(B)e”
classes
• est de type statique B
Types et
polymorphisme
• et a pour valeur (à l’exécution), autant que possible, la “même” que e.
Le système de types
Sous-typage • L’expression “(B)e” passe la compilation à condition que (au choix) :
Transtypage
Polymorphisme(s) • A et B soient des types référence avec A <: B ou B <: A ;
Surcharge
Interfaces • que A et B soient des types primitifs tous deux différents de boolean 153 ;
Héritage • que A soit un type primitif et B la version emballée de A ;
Généricité • ou que B soit un type primitif et A la version emballée d’un sous-type de B
Gestion des (combinaison implicite d’unboxing et upcasting).
erreurs et
exceptions • Même quand le programme compile, effets indésirables possibles à l’exécution :
Interfaces
graphiques
• perte d’information quand on convertit une valeur primitive ;
Concurrence
• ClassCastException quand on tente d’utiliser un objet en tant qu’objet d’un type
qu’il n’a pas.
153. NB : (char)((byte)0) est légal, alors qu’il n’y a pas de sous-typage dans un sens ou l’autre.
Compléments
en POO Subtilités du transtypage
Aldric Degorre Cas des types primitifs : possible perte d’information (2)

Introduction

Style

Objets et • Cas avec perte d’information possible :


classes
• tous les downcastings primitifs ;
Types et
polymorphisme • upcastings de int vers float 154 , long vers float ou long vers double ;
Le système de types
Sous-typage
• upcasting de float vers double hors contexte strictfp 155 .
Transtypage
Polymorphisme(s)
Surcharge
• Cas sans perte d’information : (= les autres cas = les “vrais” upcastings)
Interfaces
• upcasting d’entier vers entier plus long ;
Héritage
• upcasting d’entier ≤ 24 bits vers float et double ;
Généricité
• upcasting d’entier ≤ 53 bits vers double ;
Gestion des
erreurs et
• upcasting de float vers double sous contexte strictfp.
exceptions

Interfaces
graphiques

Concurrence
154. Par exemple, int utilise 32 bits, alors que la mantisse de float n’en a que 24 (+ 8 bits pour la position
de la virgule) → certains int ne sont pas représentables en float.
155. Selon implémentation, mais pas de garantie. Cherchez à quoi sert ce mot-clé !
Compléments
en POO Subtilités du transtypage
Aldric Degorre Cas des types primitfs : points d’implémentation

Introduction

Style

Objets et
• Pour upcasting d’entier de ≤ 32 bits vers ≤ 32 bits : dans code-octet et JVM,
classes
granularité de 32 bits → tous les “petits” entiers codés de la même façon →
Types et
polymorphisme aucune modification nécessaire. 156
Le système de types
Sous-typage
Transtypage
• Pour une conversion de littéral 157 , le compilateur fait lui-même la conversion et
Polymorphisme(s)
Surcharge
remplace la valeur originale par le résultat dans le code-octet.
Interfaces
• Dans les autres cas, conversions à l’exécution dénotées, dans le code-octet, par des
Héritage
instructions dédiées :
Généricité

Gestion des
• downcasting : d2i, d2l, d2f, f2i, f2l, i2b, i2c, i2s, l2i
erreurs et • upcasting avec perte : f2d (sans strictfp), i2f, l2d, l2f
exceptions
• upcasting sans perte : f2d (avec strictfp), i2l, i2d
Interfaces
graphiques

Concurrence

156. C’est du sous-typage inclusif, comme pour les types référence !


Supplément 157. Littéral numérique = nombre écrit en chiffres dans le code source.
Compléments
en POO Subtilités du transtypage
Aldric Degorre Cas des types primitfs : points d’implémentation

Introduction

Style

Objets et
classes

Types et
Et concrètement, que font les conversions ?
polymorphisme
Le système de types
Sous-typage
• Upcasting d’entier ≤ 32 bits vers long ( i2l ) : on complète la valeur en recopiant le
Transtypage
Polymorphisme(s)
bit de gauche 32 fois. 158
Surcharge
Interfaces • Downcasting d’entier vers entier n bits (i2b, i2c, i2s, l2i) : on garde les n bits de
Héritage
droite et on remplit à gauche en recopiant 32 − n fois le bit le plus à gauche
Généricité
restant. 159
Gestion des
erreurs et
exceptions

Interfaces
graphiques

Concurrence

158. Pour plus d’explications : chercher “représentation des entiers en complément à 2”.
Supplément 159. Ainsi, la valeur d’origine est interprétée modulo 2n sur un intervalle centré en 0.
Compléments
en POO Subtilités du transtypage
Aldric Degorre Cas des types primitfs : exemples

Introduction

Style
• short s = 42; : 42 étant représentable sur 16 bits, ne demande pas de
Objets et précaution particulière. Le compilateur compile “tel quel”.
classes

Types et
• int i = 42; short s = i; : pour copier un int dans un short, on doit le
polymorphisme
Le système de types
rétrécir. La valeur à convertir est inconnue à la compilation → ce sera fait à
Sous-typage
Transtypage
l’exécution. Ainsi le compilateur insère l’instruction i2s dans le code-octet.
Polymorphisme(s)
Surcharge • short s = 42; int i = s; : comme un short est représenté comme un int,
Interfaces

Héritage
il n’y a pas de conversion à faire (s2i n’existe pas).
Généricité • float x = 9; : ici on convertit une constante littérale entière en flottant. Le
Gestion des
erreurs et
compilateur fait lui-même la conversion et met dans le code-octet la même chose
exceptions que si on avait écrit float x = 9.0f;
Interfaces
graphiques • Mais si on écrit int i = 9; float x = i;, c’est différent. Le compilateur ne
Concurrence pouvant pas convertir lui-même, il insère i2f avant l’instruction qui va copier le
sommet de la pile dans x.
Exemple
Compléments
en POO Subtilités du transtypage
Aldric Degorre Cas des types référence

Introduction
Types références : exécuter un transtypage ne modifie pas l’objet référencé 160
Style

Objets et
classes
• downcasting : le compilateur ajoute une instruction checkcast dans le code-octet.
Types et À l’exécution, checkcast lance une ClassCastException si l’objet référencé par
polymorphisme
Le système de types le sommet de pile (= valeur de l’expression “castée”) n’est pas du type cible.
Sous-typage
Transtypage
Polymorphisme(s)
// Compile et s'exécute sans erreur :
Surcharge Comestible x = new Fruit(); Fruit y = (Fruit) x;}
Interfaces
// Compile mais ClassCastException à l'exécution :
Héritage Comestible x = new Viande(); Fruit y = (Fruit) x;
Généricité
// Ne compile pas !
// Viande x = new Viande(); Fruit y = (Fruit) x;
Gestion des
erreurs et
exceptions
• upcasting : invisible dans le code-octet, aucune instruction ajoutée
Interfaces
graphiques
→ pas de conversion réelle à l’exécution. car l’inclusion des sous-types garantit,
Concurrence
dès la compilation, que le cast est correct (sous-typage inclusif).
160. en particulier, pas son type : on a déjà vu que la classe d’un objet était définitive
Compléments
en POO Subtilités du transtypage
Aldric Degorre Cas des types référence

Introduction

Style • Ainsi, après le cast, on sait que l’objet “converti” est une instance du type cible 161 .
Objets et
classes • Les méthodes effectivement appelées sur un objet donné, avant ou après le cast,
Types et sont toujours celles définies dans 162 sa classe 163 .
polymorphisme
Le système de types
Sous-typage
Ce qui change : ce sont les méthodes qu’on a le droit d’appeler (dépendent du type
Transtypage
Polymorphisme(s)
statique de l’expression et ignorent son type dynamique).
Surcharge

Dans l’exemple ci-dessous, c’est bien la méthode paye() de la classe Manager qui est
Interfaces

Héritage

Généricité
appelée sur la variable e de type Employee :
Gestion des Employee e = new Manager(2500, 591); // upcasting
erreurs et
exceptions
// ici, e: type statique Employee, type dynamique Manager
System.out.println("Paye␣:␣" + e.paye()); // affichera bien 3091
Interfaces
graphiques

Concurrence 161. Sans que l’objet n’ait jamais été modifié par le cast !
162. ou, à défaut, “héritées par”
163. Ainsi elles ne dépendent pas du type statique de l’expression (principe de la liaison dynamique)
Compléments
en POO Polymorphisme
Aldric Degorre Un principe fondamental de la POO

Introduction

Style Definition (Polymorphisme)


Objets et
classes Une instruction/une méthode/une classe/... est dite polymorphe si elle peut travailler sur
Types et
polymorphisme
des données de types concrets différents, qui se comportent de façon similaire.
Le système de types
Sous-typage
Transtypage
Polymorphisme(s)
Surcharge
• Le fait pour un même morceau de programme de pouvoir fonctionner sur des types
Interfaces
concrets différents favorise de façon évidente la réutilisation.
Héritage

Généricité
• Or tout code réutilisé, plutôt que dupliqué quasi à l’identique, n’a besoin d’être
Gestion des corrigé qu’une seule fois par bug détecté.
erreurs et
exceptions • Donc le polymorphisme aide à “bien programmer”, ainsi la POO en a fait un de ses
Interfaces
graphiques
“piliers”.
Concurrence
Il y a en fait plusieurs formes de polymorphisme en Java...
Compléments
en POO Polymorphisme
Aldric Degorre Exemples et mécanismes divers

Introduction

Style
• L’opérateur + fonctionne avec différents types de nombres. C’est une forme de
Objets et polymorphisme résolue à la compilation 164 .
classes

Types et static void f(Schtroumpfable s, int n) {
polymorphisme for(int i = 0; i < n; i ++) s.schtroumpf();
Le système de types
Sous-typage
}
Transtypage
Polymorphisme(s)
Surcharge f est polymorphe : toute instance directe ou indirecte de Schtroumpfable peut
Interfaces

Héritage
être passé à cette méthode, sans recompilation !
Généricité L’appel s.schtroumpf() fonctionne toujours car, à son exécution, la JVM cherche une
Gestion des implémentation de schtroumpf dans la classe de s (liaison dynamique), or le sous-typage
erreurs et
exceptions garantit qu’une telle implémentation existe.
Interfaces
graphiques
• Et dans l’exemple suivant : System.out.println(z);, z peut être de n’importe
Concurrence
quel type. Quel(s) mécanisme(s) intervien(en)t-il(s) ?
Exemple 164. Le compilateur détecte le type des opérandes, et choisit la bonne version de “+”.
Compléments
en POO Formes de polymorphisme
Aldric Degorre Les 3 formes de polymorphisme en Java

Introduction

Style • polymorphisme ad hoc (via la surcharge) : le même code recompilé dans différents
Objets et contextes peut fonctionner pour des types différents.
classes

Types et
Attention : résolution à la compilation → après celle-ci, type concret fixé.
polymorphisme
Le système de types
Donc pas de réutilisation du code compilé → forme très faible de polymorphisme.
Sous-typage
Transtypage
Polymorphisme(s)
→ polymorphisme par sous-typage : le code peut être exécuté sur des données de
Surcharge
Interfaces
différents sous-types d’un même type (souvent une interface) sans recompilation.
Héritage → forme classique et privilégiée du polymorphisme en POO
Généricité
• polymorphisme paramétré :
Gestion des
erreurs et
exceptions Concerne le code utilisant les type génériques (ou paramétrés, cf. généricité).
Interfaces
graphiques
Le même code peut fonctionner, sans recompilation, quelle que soit la
Concurrence
concrétisation des paramètres.
Ce polymorphisme permet d’exprimer des relations fines entre les types.
Résumé
Compléments
en POO Surcharge
Aldric Degorre Définition

Introduction Surcharge = situation où existent plusieurs définitions (au choix)


Style

Objets et
• dans un contexte donné d’un programme, de plusieurs méthodes de même nom ;
classes
• dans une même classe, plusieurs constructeurs ;
Types et
polymorphisme
Le système de types
• d’opérateurs arithmétiques dénotés avec le même symbole. 165 .
Sous-typage
Transtypage
Polymorphisme(s)
Signature d’une méthode = n-uplet des types de ses paramètres formels.
Surcharge
Interfaces Remarques :
Héritage

Généricité
• Interdiction de définir dans une même classe 166 2 méthodes ayant même nom et
Gestion des même signature (ou 2 constructeurs de même signature).
erreurs et
exceptions → 2 entités surchargées ont forcément une signature différente 167 .
Interfaces 165. P. ex. : “/” est défini pour int mais aussi pour double
graphiques
166. Les méthodes héritées comptent aussi pour la surcharge. Mais en cas de signature identique, il y a
Concurrence
masquage et non surcharge. Donc ce qui est dit ici reste vrai.
167. Nombre ou type des paramètres différent ; le type de retour ne fait pas partie de la signature et n’a rien à
Révision voir avec la surcharge !
Compléments
en POO Surcharge
Aldric Degorre Résolution : définitions préalables

Introduction

Style

Objets et
classes

Types et • Une signature (p1 , . . . , pn ) subsume une signature (q1 , . . . , qm ) si n = m et


polymorphisme
Le système de types ∀i ∈ [1, n], pi :> qi .
Sous-typage
Transtypage
Polymorphisme(s)
Dit autrement : une signature subsumant une autre accepte tous les arguments
Surcharge
Interfaces
acceptés par cette dernière.
Héritage • Pour chaque appel de méthode f(e1 , e2 , ..., en ) dans un code source, la signature
Généricité
d’appel est le n-uplet de types (t1 , t2 , ..., tn ) tel que ti est le type de l’expression ei
Gestion des
erreurs et (tel que détecté par le compilateur).
exceptions

Interfaces
graphiques

Concurrence

Détails
Compléments
en POO Surcharge
Aldric Degorre Résolution (simplifiée... )

Introduction

Style Pour un appel à “f” donné, le compilateur :


Objets et
classes 1 liste les méthodes de nom f du contexte courant ;
Types et
polymorphisme 2 garde celles dont la signature subsume la signature d’appel ;
Le système de types
Sous-typage
Transtypage
3 élimine celles dont la signature subsume la signature d’une autre candidate (ne
Polymorphisme(s)
Surcharge
garde que les signatures les plus spécialisées) ;
Interfaces

Héritage 4 applique quelques autres règles 168 pour éliminer d’autres candidates ;
Généricité
5 s’il reste plusieurs candidates à ce stade, renvoie une erreur (appel ambigü) ;
Gestion des
erreurs et
exceptions
6 sinon, inscrit la référence de la dernière candidate restante dans le code octet.
Interfaces C’est celle-ci qui sera appelée à l’exécution. 169
graphiques

Concurrence
168. Notamment liées à l’héritage, nous ne détaillons pas.
169. Exactement celle-ci pour les méthodes statiques. Pour les méthodes d’instance, on a juste déterminé
Détails que la méthode qui sera choisie à l’exécution aura cette signature-là. Voir liaison dynamique.
Compléments
en POO Surcharge
Aldric Degorre Exemples

Introduction

Style
public class Surcharge {
Objets et
public static void f(double z) { System.out.println("double"); }
classes

Types et public static void f(int x) { System.out.println("int"); }


polymorphisme
Le système de types
Sous-typage public static void g(int x, double z) { System.out.println("int␣double"); }
Transtypage
Polymorphisme(s)
Surcharge public static void g(double x, int z) { System.out.println("double␣int"); }
Interfaces

Héritage public static void main(String[] args) {


Généricité
f(0); // affiche "int"
f(0d); // affiche "double"
Gestion des
// g(0, 0); ne compile pas
erreurs et
exceptions g(0d, 0); // affiche "double int"
}
Interfaces
graphiques }
Concurrence

Exemple
Compléments
en POO Surcharge
Aldric Degorre Pourquoi elle ne permet que du polymorphisme “faible”

Introduction public class PolymorphismeAdHoc {


public static void f(String s) { ... }
Style
public static void f(Integer i) { ... }
Objets et public static void g(??? o) { // <-- par quoi remplacer "???" ?
classes
f(o); // <-- instruction supposée "polymorphe"
Types et }
polymorphisme
Le système de types
}
Sous-typage
Transtypage
Polymorphisme(s)
Surcharge
g() doit être recompilée en remplaçant les ??? par String ou Integer pour accepter
Interfaces
l’un ou l’autre de ces types (mais pas les 2 dans une même version du programme).
Héritage

Généricité
Alternative, écrire la méthode, une bonne fois pour toutes, de la façon suivante :
Gestion des
erreurs et public static void g(Object o) { // méthode "réellement" polymorphe
exceptions
if (o instanceof String) f((String) o);
Interfaces else if (o instanceof Integer) f((Integer) o);
graphiques
else { /* gérer l'erreur */ }
Concurrence }

Discussion Mais ici, c’est en réalité du polymorphisme par sous-typage (de Object).
Compléments
en POO Interfaces
Aldric Degorre
Pour réaliser le polymorphisme via le sous-typage, de préférence, on définit une
Introduction
interface, puis on la fait implémenter par plusieurs classes.
Style

Objets et Plusieurs façons de voir la notion d’interface :


classes

Types et • supertype de toutes les classes qui l’implémentent :


polymorphisme
Le système de types Si la classe Fruit implémente l’interface Comestible, alors on a le droit d’écrire :
Sous-typage
Transtypage
Comestible x = new Fruit();
Polymorphisme(s)
Surcharge

(parce qu’alors Fruit <: Comestible)


Interfaces

Héritage

Généricité
• contrat qu’une classe qui l’implémente doit respecter
Gestion des (ce contrat n’est pas entièrement écrit en Java, cf. Liskov substitution principle).
erreurs et
exceptions • type de tous les objets qui respectent le contrat.
Interfaces
graphiques • mode d’emploi pour utiliser les objets des classes qui l’implémentent.
Concurrence
Rappel : type de données ↔ ce qu’il est possible de faire avec les données de ce type.
Analyse En POO → messages qu’un objet de ce type peut recevoir (méthodes appelables)
Compléments
en POO Interfaces : syntaxe de la déclaration
Aldric Degorre
public interface Comparable { int compareTo(Object other); }
Introduction

Style
Déclaration comme une classe, en remplaçant class par interface, mais :
Objets et
classes
• constructeurs interdits ;
Types et • tous les membres implicitement 170 public ;
polymorphisme
Le système de types • attributs implicitement static final (= constantes) ;
Sous-typage
Transtypage
Polymorphisme(s)
• types membres d’une interface nécessairement static ;
Surcharge
Interfaces
• méthodes d’instance implicitement abstract (méthodes abstraites : seulement
Héritage déclarées, pas définies, c.-à-d. juste signature suivie de “;”, sans corps) ;
Généricité • Java < 8 : toutes les méthodes sont abstraites (dont non statiques) ;
Gestion des
erreurs et Java ≥ 8 : méthodes d’instance non-abstraites, héritables, autorisées (mot-clé
exceptions
default pour annuler abstract) ; méthodes static autorisées aussi ;
Interfaces
graphiques • Java < 9 : tous les membres obligatoirement public (pas d’encapsulation) ;
Concurrence Java ≥ 9 : méthodes private autorisées (annule public et abstract) ;
• méthodes default final interdites (donc aucune méthode final autorisée).
Détails
170. Ce qui est implicite n’a pas à être écrit dans le code, mais peut être écrit tout de même.
Compléments
en POO Interfaces : syntaxe de la déclaration
Aldric Degorre Pourquoi ces contraintes ?

Introduction

Style
Limites dues plus à l’idéologie (qui s’est assouplie) qu’à la technique. 171
Objets et
classes • À la base : interface = juste description de la communication avec ses instances.
Types et
polymorphisme • Mais, dès le début, quelques “entorses” : constantes statiques, types membres.
Le système de types
Sous-typage
Transtypage
• Java 8 permet qu’une interface contienne des implémentations → la construction
Polymorphisme(s)
Surcharge
interface va au delà du concept d’“interface” de POO.
Interfaces

Héritage
• Java 9 ajoute l’idée que ce qui n’appartient pas à l’“interface” (selon POO) peut bien
Généricité être privé (pour l’instant seulement méthodes).
Gestion des
erreurs et Ligne rouge pas encore franchie : une interface ne peut pas imposer une implémentation
exceptions
à ses sous-types (interdits : constructeurs, attributs d’instance et méthodes final).
Interfaces
graphiques
Formellement, une interface n’est pas non plus directement 172 instanciable.
Concurrence

171. Il y a cependant des vraies contraintes techniques, notamment liées à l’héritage multiple.
Discussion 172. Mais très facile et presque direct via classe anonyme : new UneInterface(){ ... }.
Compléments
en POO Interfaces : usage (1)
Aldric Degorre
public interface Comparable { int compareTo(Object other); }
Introduction
class Mot implements Comparable {
Style private String contenu;
Objets et
classes public int compareTo(Object other) {
Types et return ((Mot) autreMot).contenu.length() - contenu.length();
polymorphisme }
Le système de types
Sous-typage
}
Transtypage
Polymorphisme(s)
Surcharge
Interfaces
• Mettre implements I dans l’en-tête de la classe A pour implémenter l’interface I.
Héritage • Les méthodes de I sont définissables dans A. Ne pas oublier d’écrire public.
Généricité
• Pour obtenir une “vraie” classe (non abstraite, i.e. instanciable) : nécessaire de
Gestion des
erreurs et définir toutes les méthodes abstraites promises dans l’interface implémentée.
exceptions

Interfaces
• Si toutes les méthodes promises ne sont pas définies dans A, il faut précéder la
graphiques
déclaration de A du mot-clé abstract (classe abstraite, non instanciable)
Concurrence
• Une classe peut implémenter plusieurs interfaces :
Révision class A implements I, J, K { ... }.
Compléments
en POO Interfaces : usage (2)
Aldric Degorre une méthode de tri polymorphe

Introduction

Style

Objets et public class Tri {


classes static void trie(Comparable [] tab) {
Types et /* ... algorithme de tri
polymorphisme utilisant tab[i].compareTo(tab[j])
Le système de types ...
Sous-typage
Transtypage
*/
Polymorphisme(s) }
Surcharge
Interfaces
public static void main(String [] argv) {
Héritage
Mot [] tableau = creeTableauMotsAuHasard();
Généricité // on suppose que creeTableauMotsAuHasard existe
Gestion des trie(tableau);
erreurs et // Mot [] est compatible avec Comparable []
exceptions }
Interfaces }
graphiques

Concurrence

Exemple
Compléments
en POO Interfaces : méthodes par défaut (Java ≥ 8)
Aldric Degorre

Introduction

Style • Méthode par défaut : méthode d’instance, non abstraite, définie dans une interface.
Objets et Sa déclaration est précédée du mot-clé default.
classes

Types et • N’utilise pas les attributs de l’objet, encore inconnus, mais peut appeler les autres
polymorphisme
Le système de types méthodes déclarées, même abstraites.
Sous-typage
Transtypage
Polymorphisme(s)
• Utilité : implémentation par défaut de cette méthode, héritée par les classes qui
Surcharge
Interfaces
implémentent l’interface → moins de réécriture.
Héritage • Possibilité d’une forme (faible) d’héritage multiple (via superclasse + interface(s)
Généricité
implémentée(s)).
Gestion des
erreurs et
exceptions
• Avertissement : héritage possible de plusieurs définitions pour une même méthode
Interfaces par plusieurs chemins.
graphiques
Il sera parfois nécessaire de “désambiguër” (on en reparlera).
Concurrence
Compléments
en POO Interfaces : méthodes par défaut
Aldric Degorre Exemple

Introduction
interface ArbreBinaire {
Style
ArbreBinaire gauche();
Objets et ArbreBinaire droite();
classes
default int hauteur() {
Types et ArbreBinaire g = gauche();
polymorphisme int hg = (g == null)?0:g.hauteur();
Le système de types
Sous-typage ArbreBinaire d = droite();
Transtypage int hd = (d == null)?0:d.hauteur();
Polymorphisme(s)
Surcharge
return 1 + (hg>hd)?hg:hd;
Interfaces }
Héritage
}

Généricité

Gestion des Remarque : on ne peut pas (re)définir par défaut des méthodes de la classe Object
erreurs et
exceptions (comme toString et equals).
Interfaces
graphiques Raison : une méthode par défaut n’est là que... par défaut. Toute méthode de même nom
Concurrence héritée d’une classe est prioritaire. Ainsi, une implémentation par défaut de toString
serait tout le temps ignorée.
Exemple
Compléments
en POO Héritage d’implémentations multiples
Aldric Degorre À cause des méthodes par défaut des interfaces

Introduction
Une classe peut hériter de plusieurs implémentations d’une même méthode, via les
Style
interfaces qu’elle implémente (méthodes default, Java ≥ 8).
Objets et
classes
Cela peut créer des ambiguïtés qu’il faut lever. Par exemple, le programme ci-dessous
Types et
polymorphisme est ambigu et ne compile pas (quel sens donner à new A().f() ?).
Le système de types
Sous-typage
Transtypage
i n t e r f a c e I { d e f a u l t v o i d f ( ) { System . o u t . p r i n t l n ( ” I ” ) ; } }
Polymorphisme(s) i n t e r f a c e J { d e f a u l t v o i d f ( ) { System . o u t . p r i n t l n ( ” J ” ) ; } }
Surcharge
class A implements I , J { }
Interfaces

Héritage

Généricité Pour le corriger, il faut redéfinir f() dans A, par exemple comme suit :
Gestion des
erreurs et class A implements I , J {
exceptions @Override p u b l i c void f ( ) { I . super . f ( ) ; J . super . f ( ) ;
Interfaces
}
graphiques

Concurrence
Cette construction NomInterface.super.nomMethode() permet de choisir quelle
Détails
version appeler dans le cas où une même méthode serait héritée de plusieurs façons.
Compléments
en POO Héritage d’implémentations multiples
Aldric Degorre À cause des méthodes par défaut des interfaces

Introduction

Style
Quand une implémentation de méthode est héritée à la fois d’une superclasse et d’une
Objets et
classes interface, c’est la version héritée de la classe qui prend le dessus.
Types et
polymorphisme Java n’oblige pas à lever l’ambiguïté dans ce cas.
Le système de types
Sous-typage
Transtypage
interface I {
Polymorphisme(s) default void f() { System.out.println("I"); }
Surcharge
}
Interfaces

Héritage class B {
Généricité public void f() { System.out.println("B"); }
Gestion des }
erreurs et
exceptions class A extends B implements I {}
Interfaces
graphiques

Concurrence Ce programme compile et new A().f(); affiche B.

Détails
Compléments
en POO Du bon usage des interfaces
Aldric Degorre Programmez à l’interface

Introduction
• Quand vous programmez une classe A, évitez de nommer explicitemement une
Style
autre classe B utilisée par A, notamment :
Objets et
classes • s’il est envisageable de changer un jour l’implémentation utilisée pour la
Types et fonctionnalité fournie par B,
polymorphisme
Le système de types
• pour les types de retour des méthodes publiques,
Sous-typage • pour les types des paramètres des constructeurs et méthodes publiques,
Transtypage
Polymorphisme(s) • pour les types des attributs publics.
Surcharge
Interfaces
• À la place, dépendez d’une interface I implémentée par cette classe (p. ex :
Héritage
remplacez ArrayList par List).
Généricité

Gestion des
• Avantages :
erreurs et
exceptions → A sera plus adaptable ( = polymorphe) à différentes utilisations par d’autres classes.
Interfaces → A sera plus évolutive (on peut changer l’implémentation de I sans changer l’API de A).
graphiques

Concurrence
Comme il faut bien in fine, travailler sur des instances concrètes, celles-ci doivent être
Discussion fournies par les utilisateurs de la classe (injection de dépendance).
Compléments
en POO Du bon usage des interfaces
Aldric Degorre Inversion de dépendance

Introduction

Style

Objets et
classes • Cas particulier : s’il n’existe pas d’interface répondant à vos besoins 173 :
Types et
polymorphisme → il faut créer l’interface et la joindre à votre package pour permettre à un
Le système de types
Sous-typage
utilisateur de l’implémenter et d’en passer des instances à votre classe : c’est le
Transtypage
Polymorphisme(s)
principe d’inversion de dépendance 174 .
Surcharge
Interfaces • Pourquoi “inversion” ?
Héritage
Parce que le package qui fournit la fonctionnalité de haut niveau ne dépend plus de
Généricité

Gestion des
celui qui fournit l’implémentation du composant de bas niveau : maintenant c’est le
erreurs et contraire.
exceptions

Interfaces
graphiques

Concurrence

173. Ou simplement parce que vous voulez avoir le contrôle de l’évolution de cette interface.
Discussion 174. Le “D” de SOLID
Compléments
en POO Héritage
Aldric Degorre Obtenir réutilisation et sous-typage avec élégance

Introduction

Style

Objets et
classes
• L’héritage est un mécanisme pour définir une nouvelle classe 175 B à partir d’une
Types et
polymorphisme classe existante A : B récupère les caractéristiques 176 de A et en ajoute de
Héritage
Intérêt et
nouvelles.
avertissements
Relation d’héritage • Ce mécanisme permet la réutilisation de code.
Héritage des membres

• L’héritage implique le sous-typage : les instances de la nouvelle classe sont 177 ainsi
Héritage des membres
Liaisons statique et
dynamique
abstract et final
Discussion
des instances (indirectes) de la classe héritée avec quelque chose en plus.
Énumérations

Généricité

Gestion des
erreurs et
exceptions

Interfaces 175. Ou bien une nouvelle interface à partir d’une interface existante.
graphiques
176. concrètement : les membres
Concurrence 177. Par opposition au mécanisme de composition : dans ce cas, on remplacerait “sont” par “contiennent”.
Compléments
en POO Héritage – syntaxe…
Aldric Degorre … par l’exemple

Introduction c l a s s Employee {
Style
p r i v a t e double s a l a i r e ;
p u b l i c Employee ( double s ) { s a l a i r e = s ; }
Objets et p u b l i c double paye ( ) { r e t u r n s a l a i r e ; }
classes
}
Types et
polymorphisme
c l a s s Manager e x t e n d s E m p l o y e e { / / <−−−−− c ’ e s t l à que ç a se p a s s e !
Héritage p r i v a t e d o u b l e bonus ;
Intérêt et
avertissements
Relation d’héritage p u b l i c Manager ( d o u b l e s , d o u b l e b ) {
Héritage des membres
Héritage des membres
s u p e r ( s ) ; / / <−−−−− a p p e l du c o n s t r u c t e u r p a r e n t
Liaisons statique et bonus = b ;
dynamique
abstract et final
}
Discussion
Énumérations
p u b l i c v o i d s e t B o n u s ( d o u b l e b ) { bonus = b ; }
Généricité

Gestion des @Override


erreurs et p u b l i c d o u b l e p a y e ( ) { / / <−−−−− r e d é f i n i t i o n !
exceptions
r e t u r n s u p e r . p a y e ( ) + bonus ;
Interfaces }
graphiques }
Concurrence
Compléments
en POO Héritage
Aldric Degorre Folklore et “piliers” de la POO

Introduction

Style

Objets et
classes

Types et • D’après le folklore : “piliers” de la POO = encapsulation, polymorphisme et héritage.


polymorphisme

Héritage • Mais ce dernier principe ne définit pas du tout la POO ! 178


Intérêt et
avertissements
Relation d’héritage • Héritage : mécanisme de réutilisation de code très pratique, mais non fondamental.
Héritage des membres

• ∃ LOO sans héritage : les premières versions de Smalltalk ; le langage Go. La POO
Héritage des membres
Liaisons statique et
dynamique
abstract et final
Discussion
moderne incite aussi à préférer la composition à l’héritage. 179
Énumérations

Généricité

Gestion des
erreurs et
exceptions

Interfaces
graphiques
178. Rappel : POO = programmation faisant communiquer des objets.
Concurrence 179. Ce qui n’empêche que l’héritage soit évidemment au programme de ce cours.
Compléments
en POO Héritage
Aldric Degorre Avertissement et alternatives

Introduction
Avertissement : l’héritage, mal utilisé, est souvent source de rigidité ou de fragilité 180 .
Style

Objets et Ainsi, quand cela suffit, il convient d’envisager d’abord des alternatives plus sûres :
classes

Types et • pour réutiliser du code → composition 181 (meilleur cloisonnement/encapsulation)


polymorphisme

Héritage • pour établir du sous-typage → implémentation d’interfaces 182 183 (plus souples,
multiples supertypes directs, pas de “bagage” non voulu 184 ).
Intérêt et
avertissements
Relation d’héritage
Héritage des membres
Héritage des membres
Liaisons statique et
L’héritage se justifie seulement si on veut à la fois réutilisation et sous-typage.
dynamique
abstract et final
Discussion
Faiblesses de l’héritage et alternatives possibles seront discutées à la fin de ce chapitre.
Énumérations
180. EJ3 19 : “Design and document for inheritance or else prohibit it”
Généricité
181. EJ3 18 : “Favor composition over inheritance”
Gestion des
erreurs et
182. EJ3 20 : “Prefer interfaces to abstract classes”
exceptions 183. Dans certains langages (exemple : Rust), c’est la seule façon de faire : par choix de conception,
Interfaces l’héritage n’implique pas le sous-typage.
graphiques
184. Par exemple : si on veut implémenter les entiers en héritant des rationnels, on se “traîne” l’attribut
Concurrence dénominateur, devenu inutile.
Compléments
en POO Héritage : vocabulaire
Aldric Degorre Héritage direct ou extension

Introduction
• Une classe 185 A peut “étendre”/“hériter directement de”/“être dérivée de/être une
Style

Objets et
sous-classe directe d’une autre classe B. Nous noterons A ≺ B.
classes
(Par opposition, B est appelée superclasse directe ou classe mère de A : B ≻ A.)
Types et
polymorphisme • Alors, tout se passe comme si les membres visibles de B étaient aussi définis dans
Héritage
Intérêt et
A. On dit qu’ils sont hérités Conséquences :
avertissements
Relation d’héritage 1 toute instance de A peut être utilisée comme 186 instance de B ;
Héritage des membres
Héritage des membres 2 donc une expression de type A peut être substituée à une expression de type B.
Liaisons statique et
dynamique
abstract et final
Le système de types en tient compte : A ≺ B =⇒ A <: B (A sous-type de B).
Discussion
Énumérations • Dans le code de A, le mot-clé super est synonyme de B. Il sert à accéder aux
Généricité
membres de B, même masqués par une définition dans A (ex : super.f();).
Gestion des
erreurs et • Une classe n’a qu’une seule superclasse directe (en Java : héritage simple).
exceptions

Interfaces 185. Existe aussi héritage interface vers interface. Différence : héritage multiple.
graphiques
186. Parce qu’on peut demander à l’instance de A les mêmes opérations qu’à une instance de B (critère bien
Concurrence plus faible que le principe de substitution de Liskov !).
Compléments
en POO Héritage : vocabulaire
Aldric Degorre Héritage généralisé

Introduction

Style
Héritage (sous-entendu : “généralisé”) :
Objets et
classes
• Une classe A hérite de/est une sous-classe d’une autre classe B s’il existe une
Types et séquence d’extensions de la forme : A ≺ A1 ≺ · · · ≺ An ≺ B 187 .
polymorphisme
Notation : A ⊑ B (remarques : A ≺ B =⇒ A ⊑ B, de plus A ⊑ A).
Héritage
Intérêt et
avertissements • Par opposition, B est appelée superclasse (ou ancêtre) de A. On notera B ⊒ A.
Relation d’héritage
Héritage des membres
Héritage des membres
• Héritage implique 188 sous-typage : A ⊑ B =⇒ A <: B.
Liaisons statique et
dynamique
abstract et final
• Pourtant, une classe n’hérite pas de tous les membres visibles de tous ses ancêtres,
car certains ont pu être masqués par un ancêtre plus proche. 189
Discussion
Énumérations

Généricité
• super.super n’existe pas ! Une classe est isolée de ses ancêtres indirects.
Gestion des
erreurs et
exceptions 187. L’héritage généralisé est la fermeture transitive de la relation d’héritage direct.
Interfaces 188. Héritage ⇒déf. chaîne d’héritages directs ⇒≺⊂<: chaîne de sous-typage ⇒transitivité de <: sous-typage.
graphiques
189. C’est pourquoi je distingue héritage direct et généralisé. Attention : une instance d’une classe contient,
Concurrence physiquement, tous les attributs d’instance définis dans ses superclasses, même masqués ou non visibles.
Compléments
en POO Héritage : la racine
Aldric Degorre La classe Object

Introduction La classe Object est :


Style

Objets et • superclasse de toutes les classes ;


classes

Types et
• superclasse directe de toutes les classes sans clause extends (dans ce cas,
polymorphisme
“extends Object” est implicite) ;
Héritage
Intérêt et
avertissements
• racine de l’arbre d’héritage des classes 190 .
Relation d’héritage
Héritage des membres
Héritage des membres Et le type Object est :
Liaisons statique et
dynamique
abstract et final • supertype de tous les types références (y compris interfaces) ;
Discussion
Énumérations
• supertype direct des classes sans clause ni extends ni implements et des
Généricité

Gestion des
interfaces sans clause extends ;
erreurs et
exceptions • unique source du graphe de sous-typage des types références.
Interfaces 190. Ce graphe a un degré d’incidence de 1 (héritage simple) et une source unique, c’est donc un arbre.
graphiques
Notez que le graphe d’héritage des interfaces n’est pas un arbre mais un DAG (héritage multiple) à plusieurs
Concurrence sources et que le graphe de sous-typage des types références est un DAG à source unique.
Compléments
en POO Héritage : la racine
Aldric Degorre La classe Object : ses méthodes (1)

Introduction
Object possède les méthodes suivantes :
Style

Objets et • boolean equals(Object other) : teste l’égalité de this et other


classes

Types et • String toString() : retourne la représentation en String de l’objet 191


polymorphisme

Héritage • int hashCode() : retourne le “hash code” de l’objet 192


Intérêt et
avertissements
Relation d’héritage • Class<?> getClass() : retourne l’objet-classe de l’objet.
Héritage des membres
Héritage des membres
Liaisons statique et
• protected Object clone() : retourne un “clone” 193 de l’objet si celui-ci est
Cloneable, sinon quitte sur exception CloneNotSupportedException.
dynamique
abstract et final
Discussion
Énumérations
• protected void finalize() : appelée lors de la destruction de l’objet.
Généricité

Gestion des • et puis wait, notify et notifyAll que nous verrons plus tard (cf. threads).
erreurs et
exceptions 191. Utilisé en particulier par println et pour les conversions implicites vers String dans l’opérateur “+”.
Interfaces 192. Entier calculé de façon déterministe depuis les champs d’un objet, satisfaisant, par contrat,
graphiques
a.equals(b) =⇒ a.hashCode()== b.hashCode().
Concurrence 193. Attention : le rapport entre clone et Cloneable est plus compliqué qu’il en a l’air, cf. EJ3 Item 13.
Compléments
en POO Héritage : la racine
Aldric Degorre La classe Object : ses méthodes (2)

Introduction

Style

Objets et
Conséquences :
classes

Types et
• Grâce au sous-typage, tous les types référence ont ces méthodes, on peut donc les
polymorphisme
appeler sur toute expression de type référence.
Héritage
Intérêt et
avertissements
• Grâce à l’héritage, tous les objets disposent d’une implémentation de ces
Relation d’héritage
Héritage des membres méthodes...
Héritage des membres
Liaisons statique et ... mais leur implémentation dans Object est souvent inutile :
dynamique
abstract et final • equals : teste l’identité (égalité des adresses, comme ==) ;
Discussion
Énumérations • toString : retourne une chaîne composée du nom de la classe et du hashCode.
Généricité
→ il faut généralement redéfinir equals (et donc 194 hashCode) et toString (cf.
Gestion des
erreurs et EJ3 Items 10, 11, 12).
exceptions

Interfaces
graphiques

Concurrence 194. Rappel du transparent précédent : si a.equals(b) alors il faut a.hashCode()== b.hashCode().
Compléments
en POO Héritage et implémentation
Aldric Degorre Récapitulatif

Introduction

Style

Objets et
classes

Types et
héritage héritage implémentation sous-typage de
polymorphisme de classe d’interface d’interface types référence
Héritage
Intérêt et
mot-clé extends extends implements (tout ça)
avertissements
Relation d’héritage supertype classe interface interface type
Héritage des membres
Héritage des membres sous-type classe interface classe type
Liaisons statique et
dynamique multiplicité non oui oui oui
graphe arbre DAG DAG, hauteur 1 DAG
abstract et final
Discussion

racine(s) classe Object multiples les interfaces type Object


Énumérations

Généricité

Gestion des
erreurs et
exceptions

Interfaces
graphiques

Concurrence
Compléments
en POO Extension vs. implémentation
Aldric Degorre
En POO, en théorie :
Introduction

Style • implémentation ↔ spécification d’un supertype


Objets et
classes • héritage/extension ↔ récupération des membres hérités (= facilité syntaxique)
Types et
polymorphisme En Java (≥ 8), en pratique, la distinction s’estompe : dans les deux cas,
Héritage
Intérêt et
avertissements
• la classe implémentant une interface ou étendant une classe définit un sous-type
Relation d’héritage
Héritage des membres de cette interface ou classe
Héritage des membres
Liaisons statique et
dynamique
• et elle peut hériter de membres de ce supertype.
abstract et final

Les différences qui subsistent :


Discussion
Énumérations

Généricité

Gestion des
• extension de classe : la seule façon d’hériter de la description concrète d’un objet
erreurs et
exceptions
(attributs hérités + usage du constructeur super()) ;
Interfaces • implémentation d’interface : seule façon d’avoir plusieurs supertypes directs
graphiques

Concurrence
(“héritage multiple”).
Compléments
en POO Héritage : ajout, redéfinition, masquage
Aldric Degorre Le panorama... (1)

Introduction

Style Dans une sous-classe :


Objets et
classes • On hérite des membres visibles 195 de la superclasse directe 196 .
Types et
polymorphisme
Visible = public, protected, voire package-private, si superclasse dans même package.
Héritage • On peut masquer (to hide) n’importe quel membre hérité :
Intérêt et
avertissements
Relation d’héritage
• méthodes : par une définition de même signature
Héritage des membres
Héritage des membres
dans ce cas, le type de retour doit être identique 197 , sinon erreur de syntaxe !
Liaisons statique et • autres membres : par une définition de même nom
dynamique
abstract et final
Discussion • Les autres membres de la sous-classe sont dits ajoutés.
Énumérations

Généricité
...
Gestion des
erreurs et
exceptions 195. Il faut en fait visibles et non-private. En effet : private est parfois visible (cf. classes imbriquées).
Interfaces 196. que ceux-ci y aient été directement définis, ou bien qu’elle les aie elle-même hérités
graphiques
197. En fait, si le type de retour est un type référence, on peut retourner un sous-type. Par ailleurs il y a des
Concurrence subtilités dans le cas des types paramétrés, cf généricité.
Compléments
en POO Héritage : ajout, redéfinition, masquage
Aldric Degorre Le panorama... (2)

Introduction

Style

Objets et ...
classes

Types et • Une méthode d’instance (non statique) masquée est dite redéfinie 198 (overridden).
polymorphisme

Héritage Dans le cas d’une redéfinition, il est interdit de :


Intérêt et
avertissements • redéfinir une méthode final,
Relation d’héritage
Héritage des membres • réduire la visibilité (e.g. redéfinir une méthode public par une méthode private),
Héritage des membres
Liaisons statique et
• ajouter une exception dans la clause throws (cf. cours sur les exceptions).
dynamique
abstract et final
Discussion La notion de redéfinition est importante en POO (cf. liaison dynamique).
Énumérations

Généricité

Gestion des
erreurs et
exceptions 198. Mon parti pris : redéfinition = cas particulier du masquage. D’autres sources restreignent, au contraire,
Interfaces la définition de “masquage” aux cas où il n’y a pas de redéfinition (“masquage simple”).
graphiques
La JLS dit que les méthodes d’instance sont redéfines et jamais qu’elles sont masquées... mais ne dit pas
Concurrence non plus que le terme est inapproprié.
Compléments
en POO Héritage : ajout, redéfinition, masquage
Aldric Degorre Remarques diverses (1)

Introduction
• L’accès à toute définition visible (masquée ou pas) de la surperclasse est toujours
Style

Objets et
possible via le mot-clé super. Par ex. : super.toString().
classes
• Les définitions masquées ne sont pas effacées. En particulier, un attribut masqué
Types et
polymorphisme contient une valeur indépendante de la valeur de l’attribut qui le masque.
Héritage
Intérêt et class A { int x; }
avertissements
class B extends A { int x; }
Relation d’héritage
Héritage des membres ...
Héritage des membres B b = new B(); // <- cet objet contient deux int
Liaisons statique et
dynamique
abstract et final
Discussion • De même, les définitions non visibles des superclasses restent “portées” par les
Énumérations

Généricité
instances de la sous-classe, même si elles ne sont pas accessibles directement.
Gestion des class A { private int x; }
erreurs et
exceptions class B extends A { int y; }
...
Interfaces
graphiques
B b = new B(); // <- cet objet contient aussi deux int

Concurrence
Compléments
en POO Héritage : ajout, redéfinition, masquage
Aldric Degorre Remarques diverses (2)

Introduction • Les membres non hérités ne peuvent pas être masqués ou redéfinis, mais rien
Style n’empêche de définir à nouveau 199 un membre de même nom (= ajout).
Objets et class A { private int x ; }
classes class B extends A { i n t x ; } // a u t o r i s é !
Types et
polymorphisme
• Un ajout simple dans un contexte peut provoquer un masquage 200 dans un autre :
Héritage
Intérêt et p a c k a g e bbb ;
avertissements
p u b l i c c l a s s B e x t e n d s aaa . A {
Relation d’héritage
p u b l i c v o i d f ( ) { System . o u t . p r i n t l n ( ” B ” ) ; } / / a v e c @ O v e r r i d e , ç a ne c o m p i l e pas
Héritage des membres
}
Héritage des membres
Liaisons statique et
dynamique
abstract et final p a c k a g e aaa ;
Discussion public class A {
Énumérations v o i d f ( ) { System . o u t . p r i n t l n ( ” A ” ) ; } / / mé t h o d e p a c k a g e−p r i v a t e , i n v i s i b l e d a n s bbb
p u b l i c s t a t i c v o i d main ( S t r i n g [ ] a r g s ) {
Généricité bbb . B b = new bbb . B ( ) ;
b . f ( ) ; / / c o n t e x t e : i n s t a n c e de c l a s s e B , i c i f de bbb . B masque f de aaa . A
Gestion des ( ( A ) b ) . f ( ) ; / / c o n t e x t e : i n s t a n c e de c l a s s e A , f de aaa . A n i masqu é e n i r e d é f i n i e
erreurs et }
exceptions }
Interfaces
graphiques
199. mais il ne s’agit pas de redéfinition au sens où on l’entend en POO
Concurrence 200. La notion de masquage ne concerne pas que la situation d’héritage : p. ex., une variable locale masque
Compléments
en POO Héritage : ajout, redéfinition, masquage
Aldric Degorre Exemple
class GrandParent {
Introduction
protected static int a , b ; // v i s i b i l i t é p r o t e c t e d , a s s u r e que l ’ h é r i t a g e se f a i t b i e n
Style protected s t a t i c void g ( ) { }
protected void f ( ) { }
Objets et }
classes
class Parent extends GrandParent {
Types et p r o t e c t e d s t a t i c i n t a ; / / masque l e a h é r i t é de G r a n d P a r e n t ( t j s a c c e s s i b l e v i a s u p e r . a )
polymorphisme / / masque g ( ) h é r i t é de G r a n d P a r e n t ( t j s a p p e l a b l e v i a s u p e r . g ( ) ) .
protected s t a t i c void g ( ) { }
Héritage
/ / r e d é f i n i t f ( ) h é r i t é de G r a n d P a r e n t ( t j s a p p e l a b l e v i a s u p e r . f ( ) ) .
Intérêt et
avertissements @Override protected void f ( ) { }
Relation d’héritage }
Héritage des membres
Héritage des membres class Enfant extends Parent { @Override protected void f ( ) { } }
Liaisons statique et
dynamique
abstract et final
Discussion • La classe Enfant hérite a, g et f de Parent et b de GrandParent via Parent.
Énumérations

Généricité
• a et g de GrandParent masqués mais accessibles via préfixe GrandParent..
Gestion des • f de Parent héritée mais redéfinie dans Enfant. Appel de la version de Parent
erreurs et
exceptions avec super.f().
Interfaces
graphiques
• f de GrandParent masquée par celle de Parent mais peut être appelée sur un
Concurrence récepteur de classe GrandParent. Remarque : super.super n’existe pas.
Compléments
en POO Liaisons statique et dynamique
Aldric Degorre Non, mais sérieusement, pourquoi distinguer la redéfinition du masquage simple ?

Introduction

Style
Dans tous les cas, à la compilation, les usages d’un nom de méthode sont traduits
Objets et comme référence vers une méthode existant dans le contexte d’appel (classe).
classes

Types et
Mais, à l’exécution :
polymorphisme

Héritage • Pour les membres autres que méthodes d’instance : la méthode trouvée à la
Intérêt et
avertissements
Relation d’héritage
compilation sera effectivement appelée.
→ Mécanisme de liaison statique (ou précoce).
Héritage des membres
Héritage des membres
Liaisons statique et

• Pour les méthodes d’instance 201 : une méthode redéfinissant la méthode trouvée à
dynamique
abstract et final

la compilation sera recherchée, depuis le contexte de la classe de l’objet récepteur.


Discussion
Énumérations

Généricité → Mécanisme de liaison dynamique (ou tardive).


Gestion des
erreurs et
exceptions Ce dernier mécanisme permet le polymorphisme : un appel de méthode compilé une fois
Interfaces
graphiques
peut à chaque exécution lancer une méthode différente, selon l’objet récepteur.
Concurrence 201. Sauf méthodes privées et sauf appel avec préfixe “super.” → liaison statique.
Compléments
en POO Liaisons statique et dynamique
Aldric Degorre Exemple

Introduction

Style
class A { class B extends A {
Objets et public A() { public B() {
classes
f(); super();
Types et g(); }
polymorphisme
} static void f() { // masquage
Héritage static void f() { System.out.println("B::f");
Intérêt et
avertissements System.out.println("A::f"); }
Relation d’héritage } void g() { // redéfinition
Héritage des membres
Héritage des membres
void g() { System.out.println("B::g");
Liaisons statique et System.out.println("A::g"); }
dynamique
abstract et final
} }
Discussion }
Énumérations

Généricité

Gestion des
erreurs et
Si on fait new B();, alors on verra s’afficher
exceptions
A::f
Interfaces
graphiques B::g

Concurrence
Compléments
en POO Liaison statique
Aldric Degorre

Introduction

Style

Objets et
classes Attention : la liaison statique concerne tous les membres sauf les méthodes
Types et d’instance 202 .
polymorphisme

Héritage Lorsqu’on fait référence à un membre, lequel choisir ?


Intérêt et

Principe de la liaison statique : dès la compilation, on décide quelle définition sera


avertissements
Relation d’héritage

effectivement utilisée pour l’exécution (c.-à-d. toutes les exécutions).


Héritage des membres
Héritage des membres
Liaisons statique et

Pour l’explication, nous distinguons cependant :


dynamique
abstract et final
Discussion
Énumérations

Généricité
• d’abord le cas simple (tout membre sauf méthode)
Gestion des • ensuite le cas moins simple des méthodes (possible surcharge)
erreurs et
exceptions

Interfaces
graphiques

Concurrence 202. Sauf les méthodes d’instance private pour lesquelles la liaison est statique.
Compléments
en POO Liaison statique
Aldric Degorre Principe : cas sans surcharge (membres non-méthode)

Introduction

Style Pour trouver la bonne définition d’un membre (non méthode) de nom m, à un point donné
Objets et du programme.
classes

Types et
polymorphisme • Si m membre statique, soit C le contexte 203 d’appel de m. Sinon, soit C la classe de
Héritage l’objet sur lequel on appelle m.
Intérêt et

• On cherche dans le corps de C une définition visible et compatible (même catégorie


avertissements
Relation d’héritage

de membre, même “staticité”, même type ou sous-type... ), puis dans les types
Héritage des membres
Héritage des membres

parents de C (superclasse et interfaces implémentées), puis les parents des


Liaisons statique et
dynamique
abstract et final
Discussion
Énumérations
parents (et ainsi de suite).
Généricité
On utilise la première définition qui convient.
Gestion des
erreurs et
exceptions
Pour les méthodes : même principe, mais on garde toutes les méthodes de signature
Interfaces
compatible avec l’appel, puis on applique la résolution de la surcharge. Ce qui donne...
graphiques

Concurrence 203. le plus souvent classe ou interface, en toute généralité une définition de type
Compléments
en POO Liaison statique
Aldric Degorre Principe : cas des méthodes statiques

Introduction Dans le cas des méthodes, il faut faire attention à la surcharge.


Style

Objets et
Quelle définition de f utiliser, quand on appelle f(x1, x2, ...), avec f statique ?
classes

Types et 1 C := contexte de l’appel de la méthode (classe ou interface).


polymorphisme

Héritage 2 Soit Mf := ∅.
Intérêt et
avertissements
Relation d’héritage 3 Mf := { méthodes statiques de nom f dans C, compatibles avec x1, x2, ...}.
Héritage des membres
Héritage des membres
Liaisons statique et
4 Pour tout supertype direct P de C , Mf += { méthodes statiques de nom f dans P,
dynamique
abstract et final
compatibles avec x1, x2, ..., non masquées 204 par autre méthode dans Mf }.
Discussion
Énumérations On répète l’étape avec les supertypes des supertypes, et ainsi de suite. 205 .
Généricité
5 On choisit la méthode de Mf dont les paramètres ont les types les plus petits.
Gestion des
erreurs et
exceptions Le compilateur ajoute au code-octet l’instruction invokestatic avec pour paramètre une
Interfaces référence vers la méthode trouvée.
graphiques
204. à cause de la surcharge il peut exister des méthodes de même nom non masquées
Concurrence 205. Jusqu’aux racines du graphe de sous-typage.
Compléments
en POO Liaison dynamique
Aldric Degorre

Introduction
Lors de l’appel x.f(y), quelle définition de f choisir ?
Style

Objets et → Principe de la liaison dynamique :


classes

Types et
polymorphisme • à la compilation, une méthode de nom f et de signature compatible avec y est
Héritage recherchée depuis le type statique de x ; le compilateur ajoute au code-octet
Intérêt et
avertissements
Relation d’héritage
l’instruction invokevirtual avec, pour paramètre, une référence vers la méthode
Héritage des membres
Héritage des membres
trouvée 206 .
• à l’exécution, une redéfinition de cette méthode est cherchée depuis le type
Liaisons statique et
dynamique
abstract et final
Discussion
Énumérations
dynamique 207 de x 208 ; la redéfinition “la plus proche” sera exécutée.
Généricité

Gestion des
erreurs et
206. Pour une méthode d’instance privée ou un appel précédé de “super.”, l’instruction utilisée est
exceptions invokespecial et le comportement sera similaire à invokestatic.
Interfaces 207. Rappel : type dynamique = classe de l’objet référencé par l’expression à l’exécution.
graphiques
208. Mais pour y : seulement du type de l’expression y (à la compilation). On parle de single dispatch.
Concurrence D’autres langages que Java ont au contraire un mécanisme de multiple dispatch.
Compléments
en POO Liaison dynamique
Aldric Degorre Cas simple avec redéfinition

Introduction
• Classe dérivée : certaines méthodes peuvent être redéfinies
Style

Objets et class A { void f() { System.out.println("classe␣A"); } }


classes class B extends A { void f() { System.out.println("classe␣B"); } }
Types et public class Test {
polymorphisme public static void main(String args[]) {
Héritage B b = new B();
Intérêt et b.f(); // <-- affiche "classe B"
avertissements
Relation d’héritage }
Héritage des membres }
Héritage des membres
Liaisons statique et
dynamique
abstract et final
Discussion
• mais aussi...
Énumérations
public class Test {
Généricité
public static void main(String args[]) {
Gestion des A b = new B(); // <-- maintenant variable b de type A
erreurs et
exceptions
b.f(); // <-- affiche "classe B" quand-même
}
Interfaces
}
graphiques

Concurrence
Compléments
en POO Liaison dynamique
Aldric Degorre Cas compliqué =⇒ quelle recette ?

Introduction
Imaginons le cas suivant, avec redéfinition et surcharge :
Style
c l a s s Y1 { }
Objets et
classes c l a s s Y2 e x t e n d s Y1 { }
Types et
polymorphisme c l a s s X1 { v o i d f ( Y1 y ) { System . o u t . p r i n t ( ” X1␣ e t ␣Y1␣ ; ␣ ” ) ; } }

Héritage c l a s s X2 e x t e n d s X1 {
Intérêt et v o i d f ( Y1 y ) { System . o u t . p r i n t ( ” X2␣ e t ␣Y1␣ ; ␣ ” ) ; }
avertissements v o i d f ( Y2 y ) { System . o u t . p r i n t ( ” X2␣ e t ␣Y2␣ ; ␣ ” ) ; }
Relation d’héritage }
Héritage des membres
Héritage des membres c l a s s X3 e x t e n d s X2 { v o i d f ( Y2 y ) { System . o u t . p r i n t ( ” X3␣ e t ␣Y2␣ ; ␣ ” ) ; } }
Liaisons statique et
dynamique
public class Liaisons {
abstract et final
p u b l i c s t a t i c v o i d main ( S t r i n g a r g s [ ] ) {
Discussion
X3 x = new X3 ( ) ; Y2 y = new Y2 ( ) ;
Énumérations
/ / n o t e z t o u s l e s u p c a s t i n g s e x p l i c i t e s c i−d e s s o u s ( s e r v e n t −i l s v r a i m e n t à rien ?)
Généricité ( ( X1 ) x ) . f ( ( Y1 ) y ) ; ( ( X1 ) x ) . f ( y ) ;
( ( X2 ) x ) . f ( ( Y1 ) y ) ; ( ( X2 ) x ) . f ( y ) ;
Gestion des x . f ( ( Y1 ) y ) ; x. f(y) ;
erreurs et }
exceptions }

Interfaces
graphiques

Concurrence
Qu’est-ce qui s’affiche ?
Compléments
en POO Liaison dynamique
Aldric Degorre Recette pour choisir la bonne méthode

Introduction

Style 2 étapes :
Objets et
classes
À la compilation, on vérifie si l’appel x.f(y) est correct (i.e. bien typé).
Types et
polymorphisme
Pour cela, algorithme de recherche identique à celui de la liaison statique, mais
Héritage recherche parmi les méthodes d’instance.
Intérêt et
avertissements
Relation d’héritage
Si on trouve finalement une méthode m, alors, la 2ème étape est garantie de fonctionner :
Héritage des membres
Héritage des membres
Liaisons statique et
À l’exécution, choix effectif de la méthode à appeler. Recherche 209 de méthode comme
dynamique
abstract et final
à la liaison statique, mais :
Discussion
Énumérations
• recherche restreinte aux méthodes qui redéfinissent m 210
Généricité

Gestion des • point de départ de la recherche = classe de l’objet référencé par x.


erreurs et
exceptions

Interfaces 209. En réalité, recherche exécutée une seule fois, car résultat mis en cache.
graphiques
210. Ainsi les éventuelles surcharges existant dans la classe de l’objet mais pas dans celle de l’expression
Concurrence sont ignorées à cette étape.
Compléments
en POO Liaison dynamique
Aldric Degorre Retour sur l’exemple

Introduction c l a s s Y1 {}
c l a s s Y2 e x t e n d s Y1 { }
Style c l a s s X1 { v o i d f ( Y1 y ) { System . o u t . p r i n t ( ” X1␣ e t ␣Y1␣ ; ␣ ” ) ; } }
c l a s s X2 e x t e n d s X1 {
Objets et void f ( Y1 y ) { System . o u t . p r i n t ( ” X2␣ e t ␣Y1␣ ; ␣ ” ) ; }
classes void f ( Y2 y ) { System . o u t . p r i n t ( ” X2␣ e t ␣Y2␣ ; ␣ ” ) ; }
}
Types et c l a s s X3 e x t e n d s X2 { v o i d f ( Y2 y ) { System . o u t . p r i n t ( ” X3␣ e t ␣Y2␣ ; ␣ ” ) ; } }
polymorphisme
public class Liaisons {
Héritage
p u b l i c s t a t i c v o i d main ( S t r i n g a r g s [ ] ) {
Intérêt et
avertissements X3 x = new X3 ( ) ; Y2 y = new Y2 ( ) ;
Relation d’héritage / / n o t e z t o u s l e s u p c a s t i n g s e x p l i c i t e s c i−d e s s o u s ( s e r v e n t −i l s v r a i m e n t à rien ?)
Héritage des membres ( ( X1 ) x ) . f ( ( Y1 ) y ) ; ( ( X1 ) x ) . f ( y ) ; ( ( X2 ) x ) . f ( ( Y1 ) y ) ;
Héritage des membres ( ( X2 ) x ) . f ( y ) ; x . f ( ( Y1 ) y ) ; x. f(y) ;
Liaisons statique et }
dynamique }
abstract et final
Discussion

Affiche : X2
Énumérations
et Y1 ; X2 et Y1 ; X2 et Y1 ; X3 et Y2 ; X2 et Y1 ; X3 et Y2 ;
Généricité

Gestion des • Pour les instructions commençant par ((X1)x). : la phase statique cherche les
erreurs et
exceptions signatures dans X1 → les surcharges prenant Y2 sont ignorées à l’exécution.
Interfaces
graphiques
• Les instructions commentçant par ((X2)x). se comportent comme celles
Concurrence commençant par x. : les mêmes signatures sont connues dans X2 et X3.
Compléments
en POO Liaison dynamique
Aldric Degorre Retour sur redéfinition et surcharge

Introduction Attention aux “redéfinitions ratées” : ça peut compiler mais...


Style

Objets et • si on se trompe dans le type ou le nombre de paramètres, ce n’est pas une


classes
redéfinition 211 , mais un ajout de méthode surchargée. Erreur typique :
Types et
polymorphisme public class Object { // la ``vraie'', c.-à -d. java.lang.Object
Héritage ...
Intérêt et public boolean equals(Object obj) { return this == obj; }
avertissements
Relation d’héritage ...
Héritage des membres }
Héritage des membres
Liaisons statique et
dynamique class C /* sous-entendu : extends Object */ {
abstract et final
Discussion
public boolean equals (C obj) { return ....; } // <- c'est une surcharge, pas
Énumérations une redéfinition !
Généricité }

Gestion des
erreurs et • Recommandé : placer l’annotation @Override devant une définition de méthode
exceptions

Interfaces
pour demander au compilateur de générer une erreur si ce n’est pas une redéfinition.
graphiques
Exemple : @Override public boolean equals(Object obj){ ... }
Concurrence
211. même pas un masquage
Compléments
en POO Méthodes et classes finales
Aldric Degorre
On peut déclarer une méthode avec modificateur final 212 . Exemple :
Introduction
class Employee {
Style
private String name;
Objets et . . .
classes
public final String getName() { return name; }
Types et . . .
polymorphisme
}
Héritage
Intérêt et
avertissements
Relation d’héritage
Héritage des membres
=⇒ ici, final empêche une sous-classe de Employee de redéfinir getName(). 213
Aussi possible :
Héritage des membres
Liaisons statique et
dynamique
abstract et final
Discussion final class Employee { . . . }
Énumérations

Généricité

Gestion des
=⇒ ici, final interdit d’étendre la classe Employee
erreurs et
exceptions Autre exemple dans la partie “Discussion”.
Interfaces 212. Attention : une variable peut aussi être déclarée avec le mot-clé final. Sa signification est alors
graphiques
différente : il interdit juste toute nouvelle affectation de la variable après son initialisation.
Concurrence 213. Ainsi, pour résumer, on a le droit de redéfinir les méthodes héritées non static et non final.
Compléments
en POO Méthodes et classes abstraites
Aldric Degorre
• Méthode abstraite : méthode déclarée sans être définie.
Introduction
Pour déclarer une méthode comme abstraite, faire précéder sa déclaration du
Style

Objets et
mot-clé abstract, et ne pas écrire son corps (reste la signature suivie de “;”).
classes • Classe abstraite : classe déclarée comme non directement instanciable.
Types et
polymorphisme Elle se déclare en faisant précéder sa déclaration du modificateur abstract :
Héritage abstract class A {
Intérêt et
avertissements int f(int x) { return 0; }
Relation d’héritage abstract int g(int x); // <- oh, une méthode abstraite !
Héritage des membres
Héritage des membres
}
Liaisons statique et

• Le lien entre les 2 : une méthode abstraite ne peut être pas déclarée dans un type
dynamique
abstract et final
Discussion
Énumérations directement instanciable → seulement dans interfaces et classes abstraites.
Généricité Interprétation : tout objet instancié doit connaître une implémentation pour
Gestion des
erreurs et
chacune de ses méthodes.
exceptions
• Une méthode abstraite a vocation à être redéfinie dans une sous-classe.
Interfaces
graphiques Conséquence : abstract static, abstract final et abstract private
Concurrence sont des non-sens !
Compléments
en POO Méthodes et classes abstraites
Aldric Degorre Exemple
abstract class Figure {
Introduction
P o i n t 2 D c e n t r e ; S t r i n g nom ; / / a u t r e s a t t r i b u t s é v e n t u e l l e m e n t
Style abstract i n t getVertexNumber ( ) ;
abstract Point2D getVertex ( i n t i ) ;
Objets et double perimeter ( ) {
classes double p e r i = 0 ;
Point2D courant = getVertex (0) ;
Types et f o r ( i n t i =1 ; i < getVertexNumber ( ) ; i ++) {
polymorphisme Point2D suivant = getVertex ( i ) ;
p e r i += c o u r a n t . d i s t a n c e ( s u i v a n t ) ;
Héritage
courant = suivant ;
Intérêt et
avertissements }
Relation d’héritage return peri + courant . distance ( getVertex (0) ) ;
Héritage des membres }
Héritage des membres }
Liaisons statique et
dynamique f i n a l class T r i a n g l e extends Figure {
abstract et final Point2D a , b , c ;
Discussion @Override i n t getVertexNumber ( ) {
Énumérations return 3 ;
}
Généricité
@Override Point2D getVertex ( i n t i ) {
Gestion des switch ( i ) {
erreurs et case 0 : r e t u r n a ;
exceptions case 1 : r e t u r n b ;
case 2 : r e t u r n c ;
Interfaces d e f a u l t : t h r o w new N o S u c h E l e m e n t E x c e p t i o n ( ) ;
graphiques }
}
Concurrence }
Compléments
en POO Classes vs. classes abstraites vs. interfaces
Aldric Degorre Complétons le tableau...

Introduction classe classe interface


Style non abstraite abstraite
Objets et
classes
déf. le contenu de ses instances complétable 214 complétable non
Types et
définit leur comportement complétable complétable complétable
polymorphisme
peut spécifier sans définir 215 non oui oui
Héritage
Intérêt et
est instanciable oui non non
peut typer une expression oui oui oui
avertissements
Relation d’héritage

peut typer un objet oui supertype supertype


Héritage des membres
Héritage des membres

contient données statiques oui oui constantes


Liaisons statique et
dynamique
abstract et final
Discussion contient comportement statique oui oui oui
Énumérations

Généricité
permet l’encapsulation oui oui non
Gestion des
héritabilité vers classe simple simple multiple 216
erreurs et
exceptions
héritabilité vers interface non non multiple
Interfaces racine unique de l’héritage oui : Object oui : Object non
graphiques 214. Par complétable, on entend que ces aspects peuvent être complétés et/ou redéfinis via héritage.
Concurrence 215. C.-à-d. présence de méthodes abstraites.
Compléments
en POO abstract class et final class
Aldric Degorre le bon usage (1)

Introduction
• abstract et final contraignent la façon dont une classe s’utilise.
Style

Objets et • Pourquoi contraindre ? → pour empêcher une utilisation incorrecte non prévue,
classes

Types et
comme dans l’exemple précédent !
polymorphisme

Héritage Plus précisément :


Intérêt et
avertissements
Relation d’héritage
Héritage des membres
• final, en figeant les méthodes (une ou toutes) d’une classe, permet d’assurer des
Héritage des membres
Liaisons statique et
propriétés qui resteront vraies pour toutes les instances de la classe.
dynamique
abstract et final
Discussion
• abstract (appliqué à une classe 217 ) empêche qu’une classe qui ne représenterait
Énumérations
qu’une implémentation incomplète ne soit instanciée directement.
Généricité

Gestion des
erreurs et
Dans les deux cas, on interdit l’existence d’objets absurdes (respectivement incohérents
exceptions ou incomplets) au sein d’un type.
Interfaces 217. abstract appliqué à une méthode, n’est pas une contrainte, simplement un marqueur pour dire que
graphiques
l’implémentation est manquante, quoique cela “contraigne” à marquer la classe comme abstract elle
Concurrence aussi.
Compléments
en POO abstract class et final class
Aldric Degorre le bon usage (2)

Introduction Constat : une classe ouverte (= extensible = non finale) correspond typiquement à une
Style
implémentation complétable, donc probablement incomplète. 218
Objets et
classes
D’après cette considération, une classe ouverte non abstraite est louche ! 219 . Comme
Types et
polymorphisme abstract final est exclus d’office, toute classe devrait alors être soit (juste)
Héritage abstract soit (juste) final.
Intérêt et
avertissements
Relation d’héritage
Héritage des membres pas abstract abstract
pas final louche OK
Héritage des membres
Liaisons statique et
dynamique
abstract et final final OK interdit
Discussion
Énumérations
218. Ce n’est pas forcément vrai : il est possible qu’une classe propose un comportement par défaut tout à
Généricité
fait valable, tout en laissant la porte ouverte à des modifications.
Gestion des
erreurs et
Néanmoins, pour ce genre de classe, il est important de documenter quelles méthodes réutilisent quelles
exceptions méthodes redéfinissables, afin de savoir quelles seront les conséquences d’une redéfinition... et aussi
Interfaces d’éviter des erreurs bêtes, comme des appels mutuellement récursifs non voulus. La documentation pourra
graphiques aussi donner une spécification des méthodes redéfinissables, qui permette de conserver un comportement
Concurrence globalement correct.
Compléments
en POO abstract class et final class
Aldric Degorre le bon usage (3)

Introduction Exemple (à ne pas faire !) :


Style
class Personne {
Objets et public String getNom() { return null; } // mauvaise implémentation par défaut
classes
}
Types et
polymorphisme
class PersonneImpl extends Personne {
Héritage private String nom;
Intérêt et
avertissements @Override public String getNom() { return nom; }
Relation d’héritage }
Héritage des membres
Héritage des membres

Mieux :
Liaisons statique et
dynamique
abstract et final
Discussion
Énumérations
abstract class Personne {
public abstract String getNom();
Généricité
}
Gestion des
erreurs et
exceptions
final class PersonneImpl extends Personne {
private String nom;
Interfaces
@Override public String getNom() { return nom; }
graphiques
}
Concurrence
Compléments
en POO abstract class et final class
Aldric Degorre le bon usage (4)

Introduction À ne pas faire non plus :


Style class Personne {
p r i v a t e S t r i n g prenom , nom ;
Objets et
p u b l i c S t r i n g g e t P r e n o m ( ) { r e t u r n prenom ; }
classes
p u b l i c S t r i n g getNom ( ) { r e t u r n nom ; }
Types et p u b l i c S t r i n g getNomComplet ( ) {
polymorphisme r e t u r n g e t P r e n o m ( ) + ” ␣ ” + getNom ( ) ; / / mé t h o d e s r e d é f i n i s s a b l e s −> d a n g e r !!!
}
Héritage }
Intérêt et
avertissements
Relation d’héritage
Héritage des membres Sans final, Personne est une classe de base fragile. Quelqu’un pourrait écrire :
Héritage des membres
Liaisons statique et
dynamique class Personne2 extends Personne {
abstract et final @ O v e r r i d e p u b l i c S t r i n g g e t P r e n o m ( ) { r e t u r n getNomComplet ( ) . s p l i t ( ” ␣ ” ) [ 0 ] }
Discussion @ O v e r r i d e p u b l i c S t r i n g getNom ( ) { r e t u r n getNomComplet ( ) . s p l i t ( ” ␣ ” ) [ 1 ] }
Énumérations }

Généricité

Gestion des ... puis exécuter new Personne2(...).getNom(), qui appelle getNomComplet(),
erreurs et
exceptions qui appelle getPrenom() et getNom(), qui appellent getNomComplet() qui
Interfaces
graphiques
appelle...
Concurrence Récursion infinie ! → StackOverflowError.
Compléments
en POO abstract class et final class
Aldric Degorre le bon usage (5)

Introduction

Style Une stratégie simple 220 et extrême :


Objets et
classes • Déclarer final toute classe destinée à être instanciée
Types et
polymorphisme ⇔ feuilles de l’arbre d’héritage.
Héritage
Intérêt et
• Déclarer abstract toute classe destinée à être étendue
avertissements
Relation d’héritage
Héritage des membres
⇔ nœuds internes de l’arbre d’héritage.
Héritage des membres
Liaisons statique et
Pour compléter, déclarer tous ses membres qui peuvent l’être en private ou
final pour empêcher ses sous-classes de casser les invariants déjà implémentés
dynamique
abstract et final

à ce niveau de l’héritage.
Discussion
Énumérations

Généricité

Gestion des
erreurs et
220. Simpliste, en fait : en effet, il est bien sûr possible de faire des classes non-abstract et non-final,
exceptions mais alors pensez à tout l’effort de documentation et aux diverses précautions à prendre.
Interfaces Par ailleurs : ce n’est pas parce qu’une classe est abstraite qu’elle dispense de ces précautions. Cela dit,
graphiques
quand on écrit une classe abstraite, on a généralement en tête que les méthodes abstraites vont être
Concurrence redéfinies et on a tendance à prendre ces précautions naturellement.
Compléments
en POO Héritage
Aldric Degorre Limites et avertissements (1)

Introduction

Style Héritage = construction importante en POO, mais pas la seule !


Objets et
classes
• L’héritage implique le sous-typage, mais n’est pas la technique à privilégier si le
Types et
polymorphisme polymorphisme par sous-typage est le seul effet voulu (→ préférer l’implémentation
Héritage d’interfaces 221 ).
Intérêt et
avertissements
Relation d’héritage
En effet, hériter d’une classe qui n’était pas conçue pour cela peut provoquer des
Héritage des membres
Héritage des membres
comportements inattendus, notamment à cause de mélanges de définitions écrites
Liaisons statique et
dynamique par des auteurs différents.
abstract et final
Discussion Une interface, elle, ne sert qu’à être implémentée et ne devrait normalement pas avoir
ce problème. 222
Énumérations

Généricité

Gestion des
erreurs et
exceptions 221. Dans certains langages (ex. : Rust), par choix de conception, héritage n’implique pas sous-typage →
Interfaces interfaces indispensables.
graphiques
222. ... bien que ce soit théoriquement possible si l’interface contient des méthodes par défaut mal conçues
Concurrence et/ou mal documentées.
Compléments
en POO Héritage
Aldric Degorre Limites et avertissements (2)

Introduction
• Réutilisation de fonctionnalités : souvent il suffit que l’objet qu’on veut enrichir de
Style
celles-ci contienne un autre objet qui les possède (i.e. composition d’objets) →
Objets et
classes meilleure encapsulation.
Types et
polymorphisme
En effet, ce qui est ajouté dans la classe qui réutilise ne risque pas de perturber ce
Héritage qui est déjà codé dans l’objet réutilisé.
Il faudra alors se poser la question suivante : “Est-ce qu’une instance de A est aussi
Intérêt et
avertissements
Relation d’héritage
Héritage des membres une instance de B (→ héritage), ou bien est-ce qu’elle possède une instance de B
Héritage des membres
Liaisons statique et
dynamique
(→ composition) ?”
abstract et final
Discussion
• L’héritage est parfois interdit (utilisation de classe final d’un auteur tiers).
Énumérations
• On entend souvent “L’héritage casse l’encapsulation.”.
Généricité

Gestion des
Plusieurs raisons :
erreurs et • On peut avoir accès à tout ce qui n’est pas private dans la classe mère.
exceptions
• On peut redéfinir une méthode héritée et “casser” le fonctionnement hérité si cette
Interfaces
graphiques méthode était utilisée par d’autres méthodes de la classe mère.
Concurrence
Toutefois, il existe des précautions contre ces problèmes (private, final, ...).
Compléments
en POO Alternative à l’héritage : ce qu’on entend par “composition”
Aldric Degorre Le “pilier zéro” de la POO ?

Introduction Composition : utilisation d’un objet à l’intérieur d’un autre pour réutiliser les
Style fonctionalités codées dans le premier. Exemple :
Objets et
c l a s s Vendeur {
classes
p r i v a t e d o u b l e marge ;
Types et p u b l i c V e n d e u r ( d o u b l e marge ) { t h i s . marge = marge ; }
polymorphisme p u b l i c d o u b l e v e n d ( B i e n b ) { r e t u r n b . g e t P r i x R e v i e n t ( ) * ( 1 . + marge ) ; }
}
Héritage
Intérêt et c l a s s B o u t i q u e e x t e n d s Commerce {
avertissements p r i v a t e Vendeur vendeur ;
Relation d’héritage p r i v a t e f i n a l L i s t < B i e n > s t o c k = new A r r a y L i s t < > ( ) ;
Héritage des membres p r i v a t e double caisse = 0 . ;
Héritage des membres
Liaisons statique et p u b l i c Boutique ( Vendeur vendeur ) { t h i s . vendeur = vendeur ; }
dynamique
abstract et final
p u b l i c v o i d vend ( Bien b ) {
Discussion
Énumérations
i f ( s t o c k . c o n t a i n s ( b ) ) { s t o c k . remove ( b ) ; c a i s e += v e n d e u r . v e n d ( b ) ; }
}
Généricité }

Gestion des
erreurs et
exceptions Boutique réutilise des fonctionalités de Vendeur sans en être un sous-type.
Interfaces
graphiques Ces mêmes fonctionalités pourraient aussi être réutilisées par une autre classe
Concurrence SuperMarche, qui pourrait aussi hériter de Commerce.
Compléments
en POO Objectif : ne pas hériter d’un “bagage” inutile
Aldric Degorre Exemple problématique

Introduction Mathématiquement les entiers sont sous-type des rationnels. Mais comment le coder ?
Style

Objets et
Pas terrible :
classes
public class Rationnel {
Types et private final int numerateur, denominateur;
polymorphisme
public Rationnel(int p, int q) { numerateur = p; denominateur = q; }
Héritage // + getteurs et opérations
Intérêt et
avertissements }
Relation d’héritage public class Entier extends Rationnel { public Entier (int n) { super(n, 1); } }
Héritage des membres
Héritage des membres
Liaisons statique et
dynamique
abstract et final
Ici, toute instance d’entiers contient 2 champs (certes non visibles) : numérateur et
Discussion
Énumérations
dénominateur. Or 1 seul int aurait dû suffire.
Généricité
→ utilisation trop importante de mémoire (pas très grave)
Gestion des
erreurs et
exceptions
→ risque d’incohérence à cause de la redondance (plus grave)
Interfaces
graphiques Autre problème : le type Rationnel ne peut pas être immuable (on ne peut pas ajouter
Concurrence final sinon plus possible de définir Entier).
Compléments
en POO Objectif : ne pas hériter d’un “bagage” inutile
Aldric Degorre Modélisation à l’aide d’interfaces

Introduction

Style Mieux :
Objets et
classes public interface Rationnel { int getNumer(); int getDenom(); // + operations }
public interface Entier extends Rationnel {
Types et
polymorphisme int getValeur();
default int getNumer() { return getValeur(); }
Héritage
Intérêt et
default int getDenom() { return 1; }
avertissements }
Relation d’héritage
Héritage des membres
public final class RationnelImmuable implements Rationnel {
Héritage des membres private final int numerateur, denominateur;
Liaisons statique et
dynamique
public RationnelImmuable(int p, int q) { numerateur = p; denominateur = q; }
abstract et final // + getteurs et opérations
Discussion
}
Énumérations
public final class EntierImmuable implements Entier {
Généricité
private final intValue;
Gestion des public EntierImmuable (int n) { intValue = n; }
erreurs et @Override public int getValeur() { return intValue; }
exceptions
}
Interfaces
graphiques

Concurrence
Compléments
en POO Objectif : ne pas laisser l’héritage casser l’encapsulation
Aldric Degorre Exemple problématique

Introduction
/* *
Style * R e e l P o s i t i f r e p r é s e n t e un r é e l p o s i t i f m o d i f i a b l e .
* I n v a r i a n t a g a r a n t i r : g e t V a l e u r ( ) e t r a c i n e ( ) r e t o u r n e n t t o u j o u r s un r é e l p o s i t i f .
Objets et
*/
classes class ReelPositif {
double v a l e u r ;
Types et p u b l i c R e e l P o s i t i f ( double v a l e u r ) { s e t V a l e u r ( v a l e u r ) ; }
polymorphisme p u b l i c g e t V a l e u r ( ) { r e t u r n v a l e u r ; } / / on v e u t r e t o u r >= 0
Héritage p u b l i c void s e t V a l e u r ( double v a l e u r ) {
i f ( v a l e u r < 0 ) t h r o w new I l l e g a l A r g u m e n t E x c e p t i o n ( ) ; / / c r a s h
Intérêt et
avertissements this . valeur = valeur ;
Relation d’héritage }
Héritage des membres p u b l i c d o u b l e r a c i n e ( ) { r e t u r n Math . s q r t ( v a l e u r ) ; }
Héritage des membres }
Liaisons statique et
dynamique class R e e l P o s i t i f A r r o n d i extends R e e l P o s i t i f {
abstract et final p u b l i c R e e l P o s i t i f A r r o n d i ( double v a l e u r ) { super ( v a l e u r ) ; }
Discussion p u b l i c v o i d s e t V a l e u r ( d o u b l e v a l e u r ) { t h i s . v a l e u r = Math . f l o o r ( v a l e u r ) ; }
Énumérations
}
Généricité
public class Test {
Gestion des p u b l i c s t a t i c v o i d main ( S t r i n g [ ] a r g s ) {
erreurs et R e e l P o s i t i f x = new R e e l A r r o n d i (− Math . P I ) ;
exceptions System . o u t . p r i n t l n ( x . r a c i n e ( ) ) ; / / o u c h ! ( a f f i c h e ” NaN ” )
}
Interfaces }
graphiques

Concurrence
Compléments
en POO Objectif : ne pas laisser l’héritage casser l’encapsulation
Aldric Degorre Solutions (1)

Introduction

Style 1 L’évidente : rendre valeur privé pour forcer à y accéder via getValeur() et
Objets et setValeur().
classes
Point faible : ne résiste toujours pas à certaines extensions
Types et
polymorphisme
class ReelPositifArrondi extends ReelPositif{
Héritage double valeur2; // et hop, on remplace l'attribut de la superclasse
Intérêt et
avertissements
public ReelPositifArrondi(double valeur) { this(valeur); }
Relation d’héritage public void getValeur() { return valeur2; }
Héritage des membres
public void setValeur(double valeur) { this.valeur2 = Math.floor(valeur); }
Héritage des membres
Liaisons statique et }
dynamique
abstract et final
Discussion
Énumérations
Ici, racine() peut toujours retourner NaN. Pourquoi ?
Généricité
2 La solution la plus précise : rendre valeur privé et et passer getValeur() en
Gestion des
erreurs et final. Cette solution garantit que l’invariant sera vérifié par toute classe dérivée.
exceptions

Interfaces
Point fort : on restreint le strict nécessaire pour assurer l’invariant.
graphiques
Point faible : il faut réfléchir, sinon on a vite fait de manquer une faille.
Concurrence
Compléments
en POO Objectif : ne pas laisser l’héritage casser l’encapsulation
Aldric Degorre Solutions (2)

Introduction
3 La sûre, simple mais rigide : valeur → private, ReelPositif → final.
Style

Objets et
Point fort : sans faille et très facile
classes
Point faible : on ne peut pas créer de sous-classe ReelArrondi, mais on peut
Types et
polymorphisme contourner grâce à la composition (on perd le polymorphisme par sous-typage) :
Héritage
class ReelPositifArrondi {
Intérêt et
avertissements private ReelPositif valeur;
Relation d’héritage
Héritage des membres
public ReelPositifArrondi(double valeur) { valeur = new
Héritage des membres ReelPositif(Math.floor(valeur)); }
Liaisons statique et public void getValeur() { return valeur.getValeur(); }
dynamique
abstract et final public void setValeur(double valeur) {
Discussion
this.valeur.setValeur(Math.floor(valeur)); }
Énumérations
}
Généricité

Gestion des
erreurs et
Pour retrouver le polymorphisme : écrire une interface commune à implémenter
exceptions
(argument supplémentaire pour l’utilisation systématique des interfaces).
Interfaces
graphiques → on a alors mis en œuvre le patron de conception “décorateur” 223 .
Concurrence 223. Gamma, Helm, Johnson, Vlissides. Design Patterns : Elements of Reusable Object-Oriented Software.
Compléments
en POO Objectif : simuler un héritage multiple
Aldric Degorre Via les interfaces et la composition → patron “délégation” 225

Introduction Supposons que vous ayez 2 classes : 224


Style
c l a s s PossedePropA { i n t a ; }
Objets et c l a s s PossedePropB { i n t b ; }
classes

Types et
polymorphisme Une classe ayant les 2 propriétés, comme si elle héritait de ces 2 classes, pourrait
Héritage s’écrire de la façon suivante (en gardant un sous-typage raisonnable) :
Intérêt et
avertissements i n t e r f a c e A v e c P r o p A { v o i d s e t A ( i n t newA ) ; i n t g e t A ( ) ; }
Relation d’héritage / / s i p o s s i b l e on a j o u t e ” i m p l e m e n t s A v e c P r o p A ” à P o s s e d e P r o p A
Héritage des membres
Héritage des membres i n t e r f a c e A v e c P r o p B { v o i d s e t B ( i n t newB ) ; i n t g e t B ( ) ; }
Liaisons statique et / / s i p o s s i b l e on a j o u t e ” i m p l e m e n t s A v e c P r o p B ” à P o s s e d e P r o p B
dynamique
abstract et final
c l a s s P o s s e d e P r o p A e t B i m p l e m e n t s AvecPropA , A v e c P r o p B {
Discussion
PossedePropA aProxy ; PossedePropB bProxy ;
Énumérations
v o i d s e t A ( i n t newA ) { a P r o x y . s e t A ( newA ) ; }
Généricité i n t getA ( ) { r e t u r n aProxy . getA ( ) ; }
v o i d s e t B ( i n t newB ) { b P r o x y . s e t B ( newB ) ; }
Gestion des i n t getB ( ) { r e t u r n bProxy . getB ( ) ; }
erreurs et }
exceptions

Interfaces 224. Leur contenu sera bien sûr en général bien plus fourni qu’un simple attribut int, sinon on ne
graphiques
s’amuserait pas à sortir le marteau-pilon pour écraser cette mouche.
Concurrence 225. Nom donné par les auteurs de Kotlin, pas par le “Gang of Four” (auteurs de Design Patterns)
Compléments
en POO Héritage, implémentation, composition – Que choisir ?
Aldric Degorre Résumé de bonnes pratiques

Introduction
• classe qu’on ne veut pas instancier directement → abstraite
Style
(on instanciera pas directement un mammifére : pas assez précis !)
Objets et
classes • classe instanciable → finale, sauf bonne raison bien documentée (EJ3 19)
Types et
polymorphisme
(à moins de prendre le risque de définir une sorte de chats qui serait en fait des chiens)
Héritage • juste créer des types et du sous-typage → créer et implémenter des interfaces
Intérêt et
avertissements (avantages : multiplicité possible des supertypes, pas de risque de casser l’encapsulation)
Relation d’héritage
Héritage des membres
Héritage des membres
• représenter un état → écrire une classe
Liaisons statique et
dynamique (attributs modifiables ou non avec constructeurs et accesseurs associées)
abstract et final
Discussion • partager la représentation d’un état → hériter d’une même classe abstraite
Énumérations

Généricité
(seulement si la relation “est-un”, i.e. le sous-typage, a du sens, sinon, composition EJ3 18)
Gestion des • réutiliser et combiner plusieurs représentations d’état dans une même classe
erreurs et
exceptions → composer les objets.
Interfaces • (alt.) combiner plusieurs abstractions d’état → implémenter plusieurs interfaces
graphiques

Concurrence
décrivant ces abstractions (via propriétés abstraites : couples getteur/setteur)
Compléments
en POO Types finis
Aldric Degorre Pour quoi faire ?

Introduction

Style Un type fini est un type ayant un ensemble fini d’instances, toutes définies statiquement
Objets et
classes
dès l’écriture du type, sans possibilité d’en créer de nouvelles lors de l’exécution.
Types et
polymorphisme
Certaines variables ont, en effet, une valeur qui doit rester dans un ensemble fini,
Héritage prédéfini :
Intérêt et

• les 7 jours de la semaine


avertissements
Relation d’héritage
Héritage des membres
Héritage des membres
Liaisons statique et
• les 4 points cardinaux
dynamique
abstract et final
Discussion
• les 3 (ou 4 ou plus) états de la matière
Énumérations

Généricité
• les n états d’un automate fini (dans protocole ou processus industriel, par exemple)
Gestion des
erreurs et
• les 7 nains...
exceptions

Interfaces → Situation intéressante car, théoriquement, nombre fini de cas à tester/vérifier.


graphiques

Concurrence
Compléments
en POO Types finis
Aldric Degorre Pour quoi faire ?

Introduction Pourquoi définir un type fini plutôt que réutiliser un type existant ?
Style

Objets et • Typiquement, types de Java trop grands 226 . Si utilisés pour représenter un
classes
ensemble fini, difficile 227 de prouver que les variables ne prennent pas des valeurs
Types et
polymorphisme absurdes 228 .
Héritage
Intérêt et
• Si preuve “à la main”, les typos restent possibles et le compilateur ne les verra pas.
avertissements

Avec un type fini, le compilateur garantit que la variable reste dans le bon ensemble.
Relation d’héritage
Héritage des membres
Héritage des membres
Liaisons statique et
dynamique Il pourrait aussi théoriquement vérifier l’exhaustivité des cas d’un switch (sans default) ou
abstract et final
Discussion d’un if / else if (sans else seul) : ça existe dans d’autres langages, mais javac ne le fait
pas 229 . Intérêt : éviter des default et des else que l’on sait inatteignables.
Énumérations

Généricité
226. Soit très grands (p. ex., il y a 232 ints), soit quasi-infinis (il ne peut pas exister plus de 232 références
Gestion des
erreurs et
en même temps, mais à l’exécution, un objet peut être détruit et un autre recréé à la même adresse... ).
exceptions 227. Impossible pour le cas général (indécidable).
Interfaces 228. C.-à-d. non associées à un des éléments de l’ensemble fini.
graphiques
229. Parce que le fichier définissant l’enum et celui contenant le switch sont compilés séparément. Ainsi
Concurrence de nouveaux cas peuvent être ajoutés plus tard à l’enum sans qu’il soit nécessaire de recompiler le switch.
Compléments
en POO Écrire un type fini
Aldric Degorre Comment faire ?

Introduction
• Mauvaise idée : réserver un nombre fini de constantes dans un type existant (ça ne
Style
résout pas les problèmes évoqués précédemment).
Objets et
classes Remarque : c’est ce que fait la construction enum du langage C. Les constantes
Types et
polymorphisme
déclarées sont en effet des int, et le type créé est un alias de int.
Héritage
• On a déjà vu qu’il fallait créer un nouveau type et s’assurer qu’il soit impossible de
Intérêt et
avertissements
créer des instances après la définition du type. Il faut notamment :
Relation d’héritage
Héritage des membres
• empêcher les instances directes
Héritage des membres • empêcher les instances indirectes (instanciation depuis sous-classe).
Liaisons statique et

• Bonne idée : implémenter le type fini comme classe à constructeurs privés et créer
dynamique
abstract et final

les instances du type fini comme constantes statiques de la classe :


Discussion
Énumérations

Généricité
public class Piece { // peut être final... mais le constructeur privé suffit
Gestion des private Piece() {}
erreurs et
public static final Piece PILE = new Piece(), FACE = new Piece();
exceptions
}
Interfaces
graphiques

Concurrence
→ les enum de Java sont du sucre syntaxique pour écrire cela (+ méthodes utiles).
Compléments
en POO Types énumérés
Aldric Degorre ... ou énumérations

Introduction

Style
public enum ETAT { SOLIDE, LIQUIDE, GAZ, PLASMA }

Objets et
classes
Une énumération est un type
Types et
polymorphisme

Héritage
• fini : c’est la raison d’être de cette construction ;
Intérêt et
avertissements
Relation d’héritage
• défini par un bloc synaxique enum, dans lequel sont listés les noms des éléments
Héritage des membres
Héritage des membres
(noms fixés à la définition : on parle des “constantes” de l’enum.) ;
Liaisons statique et
dynamique
abstract et final
• pour lequel l’opérateur “==” teste bien l’égalité sémantique 230
Discussion
Énumérations
(toutes les instances représentent des valeurs différentes) ;
Généricité • utilisable en argument d’un bloc switch ;
Gestion des
erreurs et • et dont l’ensemble des instances s’itère facilement :
exceptions

Interfaces
for (MonEnum val: MonEnum.values()){...}
graphiques

Concurrence 230. Pour les enums, identité et égalité sont synonymes.


Compléments
en POO Énumérations
Aldric Degorre La base

Introduction Exemple simple :


Style
public enum Day {
Objets et SUNDAY, MONDAY, TUESDAY, WEDNESDAY,
classes
THURSDAY, FRIDAY, SATURDAY
Types et }
polymorphisme

Héritage public class Test {


Intérêt et
avertissements public static void main(String[] args) {
Relation d’héritage
for (Day d : Day.values()) {
Héritage des membres
Héritage des membres
switch (d) {
Liaisons statique et case SUNDAY:
dynamique
abstract et final case SATURDAY:
Discussion System.out.println(d + ":␣sleep");
Énumérations
break;
Généricité default:
Gestion des System.out.println(d + ":␣work");
erreurs et }
exceptions
}
Interfaces }
graphiques }
Concurrence
Compléments
en POO Énumération = classe particulière
Aldric Degorre
Le bloc enum est une spécialisation du bloc class (“sucre syntaxique”) : il est possible
Introduction
d’écrire une classe équivalente sans utiliser le mot-clé enum.
Style

Objets et L’exemple précédent pourrait (presque 231 ) s’écrire :


classes
p u b l i c f i n a l c l a s s Day e x t e n d s Enum < Day > {
Types et p u b l i c s t a t i c f i n a l Day SUNDAY = new Day ( ” SUNDAY ” , 0 ) ,
polymorphisme MONDAY = new Day ( ” MONDAY ” , 1 ) , TUESDAY = new Day ( ” TUESDAY ” , 2 ) ,
Héritage WEDNESDAY = new Day ( ” WEDNESDAY ” , 3 ) , THURSDAY = new Day ( ” THURSDAY ” , 4 ) ,
Intérêt et
FRIDAY = new Day ( ” FRIDAY ” , 5 ) , SATURDAY = new Day ( ” SATURDAY ” , 6 ) ;
avertissements
Relation d’héritage p r i v a t e Day ( S t r i n g name , i n t o r d i n a l ) {
Héritage des membres s u p e r ( name , o r d i n a l ) ;
Héritage des membres }
Liaisons statique et
dynamique / / p l u s mé t h o d e s s t a t i q u e s v a l u e O f ( ) e t v a l u e s ( )
abstract et final
}
Discussion
Énumérations

Généricité
Enum<E> est la superclasse directe de toutes les classes déclarées avec un bloc enum.
Gestion des
erreurs et Elle contient les fonctionnalités communes à toutes les énumérations.
exceptions 231. En réalité, ceci ne compile pas : javac n’autorise pas le programmeur à étendre la classe Enum à la
Interfaces main. Cela est réservé aux vraies enum.
graphiques
Si on voulait vraiment toutes les fonctionnalités des enum, il faudrait réécrire les méthodes de la classe
Concurrence Enum.
Compléments
en POO Énumérations
Aldric Degorre Extensions

Introduction

Style
On peut donc y ajouter des membres, en particulier des méthodes :
Objets et public enum Day {
classes
SUNDAY, MONDAY, TUESDAY, WEDNESDAY, THURSDAY, FRIDAY, SATURDAY;
Types et public boolean isWorkDay() {
polymorphisme
switch (this) {
Héritage case SUNDAY:
Intérêt et
avertissements
case SATURDAY:
Relation d’héritage return false;
Héritage des membres
default:
Héritage des membres
Liaisons statique et return true;
dynamique
abstract et final
}
Discussion }
Énumérations public static void main(String[] args) {
Généricité for (Day d : Day.values()) {
Gestion des
System.out.println(d + ":␣" + (d.isWorkDay() ? "work" : "sleep"));
erreurs et }
exceptions }
Interfaces }
graphiques

Concurrence
Compléments
en POO Énumérations
Aldric Degorre Extensions

Introduction

Style On peut même ajouter des constructeurs (privés seulement). Auquel cas, il faut passer
Objets et les paramètres du(des) constructeur(s) à chaque constante de l’enum :
classes

Types et public enum Day {


polymorphisme SUNDAY(false), MONDAY, TUESDAY, WEDNESDAY, THURSDAY, FRIDAY, SATURDAY(false);
Héritage final boolean isWorkDay;
Intérêt et private Day(boolean work) {
avertissements
Relation d’héritage
isWorkDay = work;
Héritage des membres }
Héritage des membres
private Day() {
Liaisons statique et
dynamique isWorkDay = true;
abstract et final
}
Discussion
Énumérations public static void main(String[] args) {
for (Day d : Day.values()) {
Généricité
System.out.println(d + ":␣" + (d.isWorkDay ? "work" : "sleep"));
Gestion des }
erreurs et
exceptions }
}
Interfaces
graphiques

Concurrence
Compléments
en POO Énumérations
Aldric Degorre Extensions

Introduction

Style
Chaque déclaration de constante énumérée peut être suivie d’un corps de classe, afin
Objets et
d’ajouter des membres ou de redéfinir des méthodes juste pour cette constante.
classes
public enum Day {
Types et
polymorphisme SUNDAY { @Override public boolean isWorkDay() { return false; } },
MONDAY, TUESDAY, WEDNESDAY, THURSDAY, FRIDAY,
Héritage
Intérêt et
SATURDAY { @Override public boolean isWorkDay() { return false; } };
avertissements
Relation d’héritage
Héritage des membres
public boolean isWorkDay() { return true; }
Héritage des membres
Liaisons statique et
dynamique
public static void main(String[] args) {
abstract et final for (Day d : Day.values()) {
Discussion
System.out.println(d + ":␣" + (d.isWorkDay() ? "work" : "sleep"));
Énumérations
}
Généricité }
Gestion des }
erreurs et
exceptions

Interfaces
graphiques
Dans ce cas, la constante est l’instance unique d’une sous-classe 232 de l’enum.
Concurrence 232. Implicitement définie en même temps que la constante.
Compléments
en POO Énumérations
Aldric Degorre Types énumérés et sous-typage

Introduction
• Tous les types énumérés étendent la classe Enum 233 .
Style

Objets et Donc une énumération ne peut étendre aucune autre classe.


classes

Types et
• En revanche rien n’interdit d’écrire enum Truc implements Machin { ... }.
polymorphisme

Héritage
• Les types enum sont des classes à constructeur(s) privé(s). 234
Intérêt et
avertissements Ainsi aucune instance autre que les constantes déclarées dans le bloc enum ne
pourra jamais exister 235 .
Relation d’héritage
Héritage des membres
Héritage des membres
Liaisons statique et
dynamique • On ne peut donc pas non plus étendre un type énuméré 236 237 .
abstract et final
Discussion 233. Version exacte : l’énumération E étend Enum<E>. Voir la généricité.
Énumérations
234. Elles sont mêmes final si aucune des constantes énumérées n’est muni d’un corps de classe.
Généricité
235. Ainsi, toutes les instances d’une enum sont connues statiquement, i.e. dès la compilation.
Gestion des
erreurs et
236. On ne peut pas l’étendre “à la main”, mais des sous-classes (singletons) sont compilées pour les
exceptions constantes de l’enum qui sont munies d’un corps de classe.
Interfaces 237. Même quand l’enum n’est pas final. Regardez ce qui se passe quand vous essayez d’étendre avec
graphiques
une classe non final à constructeur privé. Comparez avec le message d’erreur obtenu lorsque vous
Concurrence essayez d’étendre une enum (avec et sans corps de classe pour ses constantes).
Compléments
en POO Énumérations
Aldric Degorre Méthodes

Introduction
Toute énumération E a les méthodes d’instance suivantes, héritées de la classe Enum :
Style

Objets et • int compareTo(E o) (de l’interface Comparable, implémentée par la classe


classes
Enum) : compare deux éléments de E (en fonction de leur ordre de déclaration).
Types et
polymorphisme
• String toString() : retourne le nom de la constante (une chaîne dont le texte
Héritage
Intérêt et
avertissements
est le nom de l’identificateur de la constante d’enum)
• int ordinal() : retourne le numéro de la constante dans l’ordre de déclaration
Relation d’héritage
Héritage des membres

dans l’enum.
Héritage des membres
Liaisons statique et
dynamique
abstract et final
Discussion
Énumérations
Par ailleurs, tout type énuméré E dispose des deux méthodes statiques suivantes :
Généricité
• static E valueOf(String name) : retourne la constante d’enum dont
Gestion des
erreurs et l’identificateur est égal au contenu de la chaîne name
exceptions

Interfaces • static E[] values() : retourne un tableau contenant les constantes de l’enum
graphiques
dans l’ordre dans lequel elles ont été déclarées.
Concurrence
Compléments
en POO Énumérations
Aldric Degorre Quand les utiliser

Introduction • Évidemment : pour implémenter un type fini (cf. intro de ce cours). Remarquez au
Style
passage toutes les erreurs potentielles si on utilisait, à la place d’une enum :
Objets et
classes • des int : tentation d’utiliser directement des littéraux numériques (1, 0, -42) peu
Types et parlants au lieu des constantes (par flemme). Risque très fort d’utiliser ainsi des
polymorphisme
valeurs sans signification associée.
Héritage • des String sous forme littérale : risque fort de faire une typo en tappant la chaîne
Intérêt et
avertissements
Relation d’héritage
entre guillemets.
Héritage des membres
Héritage des membres • Cas particulier : quand une classe ne doit contenir qu’une seule instance
(singleton) → le plus sûr pour garantir qu’une classe est un singleton c’est d’écrire
Liaisons statique et
dynamique
abstract et final
Discussion
Énumérations
une enum à 1 élément.
Généricité enum MaClasseSingleton /* insérer implements Machin */{
INSTANCE; // <--- l'instance unique !
Gestion des
erreurs et /* insérer ici tous les membres utiles */
exceptions }
Interfaces
graphiques

Concurrence Tout cela peut être fait sans les enums mais c’est fastidieux et risque d’être mal fait.
Compléments
en POO Énumérations
Aldric Degorre Bien les utiliser

Introduction
• Les enums sont bien pensés et robustes. Il est assez difficile de mal les utiliser.
Style

Objets et • Piège possible : compter sur les indices (int) ou l’ordre relatif des constantes
classes
d’une enum → fragilité en cas de mise à jour de la dépendance fournissant l’enum.
Types et
polymorphisme

Héritage Bonnes pratiques :


Intérêt et
avertissements
Relation d’héritage
• Pour celui qui programme l’enum :
• ne pas insérer des constantes au milieu ou changer l’ordre de l’enum sans raison ;
Héritage des membres
Héritage des membres
Liaisons statique et
dynamique • et surtout : documenter. Dire si l’ordre sera garanti pour les versions futures 238 . Dire si
les ordinaux seront préservés 239 . Ou bien dire explicitement le contraire.
abstract et final
Discussion
Énumérations

Généricité
• Pour celui qui programme le client :
Gestion des • Si l’enum n’est pas documentée, supposer le pire !
erreurs et
exceptions
• Si l’ordre n’est pas fixé : ne pas s’attendre à un ordre du tableau values(). Si les
Interfaces indices ne sont pas fixés, ne rien supposer sur la valeur retournée par ordinal().
graphiques
238. Pas un problème si le type représenté a un ordre naturel.
Concurrence 239. Pas un problème si le type représenté une représentation naturelle dans les entiers naturels.
Compléments
en POO Énumérations et collections
Aldric Degorre

Il existe des implémentations de collections optimisées pour les énumérations.


Introduction

Style • EnumSet<E extends Enum<E>, qui implémente Set<E> : on représente un


Objets et
classes ensemble de valeurs de l’énumération E par un champ de bits (le bit n°i vaut 0 si la
Types et constante d’ordinal i est dans l’ensemble, 1 sinon). Cette représentation est très
polymorphisme
concise et très rapide.
Héritage
Intérêt et
avertissements
Création via méthodes statiques
Relation d’héritage
Héritage des membres
Set<DAY> weekend = EnumSet.of(Day.SATURDAY, Day.SUNDAY), voire
Héritage des membres
Liaisons statique et
Set<Day> week = EnumSet.allOf(Day.class).
(Il y a d’autres méthodes utiles : ouvrez la documentation !)
dynamique
abstract et final
Discussion
Énumérations
• EnumMap<K extends Enum<K>, V> qui implémente Map<K,V> : une Map
Généricité
représentée (en interne) par un tableau dont la case d’indice i référence la valeur
Gestion des
erreurs et dont la clé est la constante d’ordinal i de l’enum K.
exceptions

Interfaces
Construire un EnumMap :
graphiques
Map<Day, Activite> edt = new EnumMap<>(Day.class);.
Concurrence
Compléments
en POO Collections : pourquoi ?
Aldric Degorre

Introduction

Style

Objets et
classes • Besoin : représenter des “paquets”, des “collections” d’objets similaires.
Types et
polymorphisme • Plusieurs genres de paquets/collections : avec ou sans doublon, accès séquentiel
Héritage
ou direct, avec ou sans ordre, etc.
Généricité
Collections
Généricité :
• Mais nombreux points communs : peuvent contenir plusieurs éléments, possibilité
introduction
Effacement de type, d’itérer, de tester l’appartenance, l’inclusion etc.
variance, tableaux
Lambda-expressions
Les “streams”
• Pour chaque “genre” plusieurs représentations/implémentations de la structure
Wildcards
(optimisant telle ou telle opération...).
Gestion des
erreurs et
exceptions

Interfaces
graphiques

Concurrence
Analyse
Compléments
en POO Collections : pourquoi ?
Aldric Degorre

Introduction L’idée n’est pas nouvelle.


Style
Les collections génériques, introduites dans Java SE 5, remplacent avantageusement :
Objets et
classes

Types et
• Les tableaux 240 .
polymorphisme
• Les “collections” non génériques 241 de Java < 5, avec leurs éléments de type
Héritage

Généricité
statique Object, qu’il fallait caster avant usage.
Collections
Généricité : Exemple : la classe Vector, représentant des tableaux dynamiques.
introduction
Effacement de type,
variance, tableaux
Lambda-expressions
Les collections justifient à elles seules l’introduction de la généricité dans Java.
Les “streams”
Wildcards
240. Qui gardent quelques avantages : syntaxe pratique, efficacité et disposent déjà d’un “genre de
Gestion des
erreurs et généricité”(un String[] contiendra des éléments String et rien d’autre), dont nous reparlerons.
exceptions
241. NB : les vieilles collections ont été complétées pour être utilisables de façon générique (Vector<E>)
Interfaces et implémenter l’interface Collection<E>.
graphiques
Utiliser de telles classes sans paramètre de type provoque désormais des avertissements à la compilation
Concurrence
→ ne le faites pas. Si vous travaillez sur du vieux code sur une JVM récente, remplacez Vector par
Vector<Object>.
Compléments
en POO La hiérarchie des sous-interfaces d’Iterable
Aldric Degorre Interfaces de java.util et java.util.concurrent 242

Introduction

Style

Objets et
classes Iterable
Types et
polymorphisme
Collection
Héritage

Généricité
Collections
Set List Queue
Généricité :
introduction
Effacement de type,
variance, tableaux SortedSet Deque BlockingQueue
Lambda-expressions
Les “streams”
Wildcards
NavigableSet BlockingDeque TransferQueue
Gestion des
erreurs et
exceptions

Interfaces Chacune de ces interfaces possède une ou plusieurs implémentations.


graphiques

Concurrence

242. Autres sous-interfaces dans java.nio.file et java.beans.beancontext.


Compléments
en POO La hiérarchie des sous-interfaces de Map
Aldric Degorre Interfaces de java.util et java.util.concurrent 243

Introduction

Style

Objets et
classes
Map
Types et
polymorphisme

Héritage SortedMap ConcurrentMap


Généricité
Collections
Généricité : NavigableMap
introduction
Effacement de type,
variance, tableaux
Lambda-expressions ConcurrentNavigableMap
Les “streams”
Wildcards

Gestion des
erreurs et
exceptions Chacune de ces interfaces possède une ou plusieurs implémentations.
Interfaces
graphiques

Concurrence

243. Autres dans javax.script, javax.xml.ws.handler et javax.xml.ws.handler.soap.


Compléments
en POO L’interface Iterable (1)
Aldric Degorre

Introduction
public interface Iterable<E> {
Style Iterator<E> iterator(); // cf. Iterator
Objets et default Spliterator<E> spliterator() { ... } // pour les Stream
classes default void forEach(Consumer<? super T> action) { ... } // utiliser avec lambdas
Types et }
polymorphisme

Héritage

Généricité
Un Iterable représente une séquence qu’on peut “itérer” (à l’aide d’un itérateur).
Collections
Généricité :
introduction • soit avec la construction “for-each” (conseillé !) :
Effacement de type,
variance, tableaux
Lambda-expressions for ( Object o : monIterable ) System.out.println(o);
Les “streams”
Wildcards

Gestion des • soit avec la méthode forEach et une lambda-expression (cf. chapitre dédié) :
erreurs et
exceptions
monIterable.forEach(System.out::println);
Interfaces
graphiques

Concurrence
Compléments
en POO L’interface Iterable (2)
Aldric Degorre
• soit en utilisant explicitement l’itérateur (rare, mais utile pour accès en écriture) :
Introduction
Iterator<String> it = monIterable.iterator();
Style
while (it.hasNext()) {
Objets et String s = it.next();
classes
if (s.equals("À␣enlever") it.remove();
Types et else System.out.println("On␣garde:␣" + s);
polymorphisme
}
Héritage

Généricité
Collections
Remarque : la construction for-each et la méthode forEach ne permettent qu’un
Généricité :
introduction
parcours en lecture seule.
Effacement de type,
variance, tableaux • soit en réduisant un Stream (cf. chapitre dédié) basé sur cet Iterable 244 :
Lambda-expressions
Les “streams”
maCollection.stream()
Wildcards
.filter(x -> !x.equals("À␣enlever"))
Gestion des .forEach(System.out::println());
erreurs et
exceptions

Interfaces Les paramètres des méthodes de Stream sont typiquement des


graphiques
lambda-expressions.
Concurrence
244. En réalité, pour des raisons assez obscures, la méthode stream n’existe que dans la sous-interface
Collection. Mais il est facile de programmer une méthode équivalente pour Iterable.
Compléments
en POO Itérateurs ?
Aldric Degorre

Introduction Un itérateur :
Style

Objets et
• sert à parcourir un itérable et est habituellement utilisé implicitement ;
classes
• s’instancie en appelant la méthode iterator sur l’objet à parcourir ;
Types et
polymorphisme
• est un objet respectant l’interface suivante :
Héritage

Généricité public interface Iterator<E> {


Collections boolean hasNext();
Généricité :
introduction E next();
Effacement de type,
variance, tableaux
void remove(); // opération optionnelle
Lambda-expressions }
Les “streams”
Wildcards

remove, si implémentée, permet de supprimer un élément en cours de parcours sans


Gestion des
erreurs et
exceptions
provoquer ConcurrentModificationException (au contraire des méthodes de
Interfaces
graphiques l’itérable). Cette possibilité justifie de créer une variable pour manipuler explicitement
Concurrence l’itérateur (sinon, on préfère for-each).
Compléments
en POO L’interface Collection
Aldric Degorre

Introduction
Une Collection est un Iterable muni des principales opérations ensemblistes :
Style public interface Collection<E> extends Iterable<E> {
Objets et int size();
classes boolean isEmpty();
Types et boolean contains(Object element);
polymorphisme boolean add(E element); // opération optionnelle
Héritage boolean remove(Object element); // opération optionnelle
boolean containsAll(Collection<?> c);
Généricité
Collections
boolean addAll(Collection<? extends E> c); // opération optionnelle
Généricité : boolean removeAll(Collection<?> c); // opération optionnelle
introduction
Effacement de type,
boolean retainAll(Collection<?> c); // opération optionnelle
variance, tableaux
void clear(); // opération optionnelle
Lambda-expressions
Les “streams” Object[] toArray();
Wildcards <T> T[] toArray(T[] a);
Gestion des default Stream<E> stream() { ... }
erreurs et }
exceptions

Interfaces
graphiques L’API ne fournit pas d’implémentation directe de Collection, mais plutôt des
Concurrence
collections spécialisées, décrites dans la suite.
Compléments
en POO L’interface Set
Aldric Degorre

Introduction • Pas de méthodes autres que celles héritées de Collection.


Style
• Différence : le contrat de Set garantit l’unicité de ses éléments (pas de doublon).
Objets et
classes

Types et Exemple :
polymorphisme

Héritage Set<Integer> s = new HashSet<Integer>();


s.add(1); s.add(2); s.add(3); s.add(1);
Généricité
Collections
for (int i : s) System.out.print(i + ",␣");
Généricité :
introduction

Ceci affichera : 1, 2, 3,
Effacement de type,
variance, tableaux
Lambda-expressions
Les “streams”
Wildcards
La classe HashSet est une des implémentations de Set fournies par Java. C’est celle
Gestion des
que vous utiliserez le plus souvent.
erreurs et
exceptions

Interfaces Unicité ? un élément x est unique si pour tout autre élément y, x.equals(y) retourne
graphiques
false.
Concurrence
⇒ importance d’avoir une redéfinition correcte de equals().
Compléments
en POO L’interface SortedSet
Aldric Degorre

Introduction Comme Set, mais les éléments sont triés.


Style

Objets et
... ce qui permet d’avoir quelques méthodes en plus.
classes
public interface SortedSet<E> extends Set<E> {
Types et
polymorphisme
// Range-view
SortedSet<E> subSet(E fromElement, E toElement);
Héritage
SortedSet<E> headSet(E toElement);
Généricité SortedSet<E> tailSet(E fromElement);
Collections
Généricité :
introduction // Endpoints
Effacement de type,
variance, tableaux E first();
Lambda-expressions E last();
Les “streams”
Wildcards
// Comparator access
Gestion des
erreurs et
Comparator<? super E> comparator();
exceptions }
Interfaces
graphiques

Concurrence
Implémentation typique : classe TreeSet.
Compléments
en POO L’interface List (1)
Aldric Degorre
List : c’est une Collection ordonnée avec possibilité de doublons. C’est ce qu’on
Introduction
utilise le plus souvent. Permet d’abstraire les notions de tableau et de liste chainée.
Style

Objets et Fonctionnalités principales :


classes

Types et
• acces positionnel (on peut accéder au i-ième élément)
polymorphisme
• recherche (si on connait un élément, on peut demander sa position)
Héritage

Généricité public interface List<E> extends Collection<E> {


Collections // Positional access
Généricité :
introduction
E get(int index);
Effacement de type, E set(int index, E element); //optional
variance, tableaux
Lambda-expressions boolean add(E element); //optional
Les “streams” void add(int index, E element); //optional
Wildcards
E remove(int index); //optional
Gestion des boolean addAll(int index,
erreurs et
exceptions
Collection<? extends E> c); //optional

Interfaces
// Search
graphiques
int indexOf(Object o);
Concurrence int lastIndexOf(Object o);
...
Compléments
en POO L’interface List (2)
Aldric Degorre

Introduction
Mais aussi :
Style

Objets et
classes
• itérateurs plus riches (peuvent itérer en arrière)
Types et
polymorphisme
• “vues” de sous-listes 245
Héritage
...
Généricité
Collections
Généricité :
// Iteration
introduction ListIterator<E> listIterator();
Effacement de type,
variance, tableaux ListIterator<E> listIterator(int index);
Lambda-expressions
Les “streams”
Wildcards
// Range-view
List<E> subList(int from, int to);
Gestion des
erreurs et }
exceptions

Interfaces
graphiques

Concurrence
245. Vue d’un objet o : objet v donnant accès à une partie des données de o sans en être une copie
(partielle), les modifications des 2 objets restent liées.
Compléments
en POO Itérateurs de liste ?
Aldric Degorre

Introduction
Un itérateur de liste sert à parcourir une liste. Il fait la même chose qu’un itérateur ; mais
Style
aussi quelques autres opérations, comme :
Objets et
classes

Types et
• parcourir à l’envers
polymorphisme
• ajouter/modifier des éléments en passant
Héritage

Généricité • un itérateur de liste est un objet respectant l’interface suivante :


Collections
Généricité :
introduction public interface ListIterator<E> extends Iterator<E>{
Effacement de type,
variance, tableaux void add(E e);
Lambda-expressions boolean hasPrevious();
Les “streams”
Wildcards
int nextIndex();
E previous();
Gestion des
erreurs et
int previousIndex();
exceptions void set(E e);
}
Interfaces
graphiques

Concurrence
Compléments
en POO L’interface List (3)
Aldric Degorre

Introduction
Implémentations principales : ArrayList (basée sur un tableau, avec
Style
redimensionnement dynamique), LinkedList (basée sur liste chainée).
Objets et
classes
Exemple :
Types et
polymorphisme
ArrayList<Integer> l = new ArrayList<Integer>();
Héritage l.add(1); l.add(2); l.add(3); l.add(1);
Généricité for (int i : l) System.out.print(i + ",␣");
Collections System.out.println("\n3e␣element:␣" + l.get(2));
Généricité :
introduction
l.set(2,9);
Effacement de type, System.out.println("Nouveau␣3e␣element:␣" + l.get(2));
variance, tableaux
Lambda-expressions
Les “streams”
Wildcards
Ceci affichera :
Gestion des
erreurs et 1, 2, 3, 1
exceptions
3e element: 3
Interfaces Nouveau 3e element: 9
graphiques

Concurrence
Compléments
en POO L’interface Queue
Aldric Degorre

Introduction

Style Une Queue représente typiquement une collection d’éléments en attente de traitement
Objets et
classes
(typiquement FIFO : first in, first out).
Types et
polymorphisme
Opérations de base : insertion, suppression et inspection.
Héritage public interface Queue<E> extends Collection<E> {
Généricité E element();
Collections boolean offer(E e);
Généricité :
introduction
E peek();
Effacement de type, E poll();
variance, tableaux
Lambda-expressions
E remove();
Les “streams” }
Wildcards

Gestion des
erreurs et
exceptions
Exemple : la classe PriorityQueue présente ses éléments selon l’ordre naturel de ses
Interfaces
éléments (ou un autre ordre si spécifié).
graphiques

Concurrence
Compléments
en POO L’interface Deque (1)
Aldric Degorre

Introduction
Deque = “double ended queue”.
Style

Objets et C’est comme une Queue, mais enrichie afin d’accéder à la collection aussi bien par le
classes
début que par la fin.
Types et
polymorphisme
Le même Deque peut ainsi aussi bien servir de structure FIFO que LIFO (last in, first out).
Héritage

Généricité
public interface Queue<E> extends Collection<E> {
Collections boolean addFirst(E e);
Généricité : boolean addList(E e);
introduction
Effacement de type, Iterator<E> descendingIterator();
variance, tableaux
Lambda-expressions
E getFirst();
Les “streams” E getLast();
Wildcards
boolean offerFirst(E e);
Gestion des boolean offerLast(E e);
erreurs et E peekFirst();
exceptions
E peekLast();
Interfaces ...
graphiques

Concurrence
Compléments
en POO L’interface Deque (2)
Aldric Degorre

Introduction

Style
...
Objets et
classes E pollFirst();
E pollLast();
Types et
polymorphisme
E pop();
void push(E e);
Héritage
E removeFirst();
Généricité E removeLast();
Collections
E removeFirstOccurrence(Object o);
Généricité :
introduction E removeLastOccurrence(Object o);
Effacement de type,
variance, tableaux ...
Lambda-expressions // plus methodes héritées
Les “streams”
Wildcards
}

Gestion des
erreurs et
exceptions Implémentations typiques : ArrayDeque, LinkedList
Interfaces
graphiques

Concurrence
Compléments
en POO L’interface Map (1)
Aldric Degorre

Introduction

Style Une Map est un ensemble d’associations (clé 7→ valeur), où chaque clé ne peut être
Objets et
classes
associée qu’à une seule valeur.
Types et
polymorphisme
Nombreuses méthodes communes avec l’interface Collection, mais particularités.
Héritage public interface Map<K,V> {
Généricité // Basic operations
Collections V put(K key, V value);
Généricité :
introduction V get(Object key);
Effacement de type, V remove(Object key);
variance, tableaux
Lambda-expressions boolean containsKey(Object key);
Les “streams” boolean containsValue(Object value);
Wildcards
int size();
Gestion des boolean isEmpty();
erreurs et
exceptions ...

Interfaces
graphiques

Concurrence
Compléments
en POO L’interface Map (2)
Aldric Degorre

Introduction
...
Style
// Bulk operations
Objets et void putAll(Map<? extends K, ? extends V> m);
classes
void clear();
Types et // Collection Views
polymorphisme
public Set<K> keySet();
Héritage public Collection<V> values();
Généricité public Set<Map.Entry<K,V>> entrySet();
Collections // Interface for entrySet elements
Généricité :
introduction public interface Entry {
Effacement de type, K getKey();
variance, tableaux
Lambda-expressions V getValue();
Les “streams” V setValue(V value);
Wildcards
}
Gestion des }
erreurs et
exceptions

Interfaces
graphiques
Implémentation la plus courante : la classe HashMap
Concurrence
Compléments
en POO L’interface SortedMap
Aldric Degorre

Introduction

Style

Objets et SortedMap est à Map ce que SortedSet est à Set : ainsi les associations sont
classes
ordonnées par rapport à l’ordre de leurs les clés.
Types et
polymorphisme
public interface SortedMap<K, V> extends Map<K, V>{
Héritage Comparator<? super K> comparator();
Généricité SortedMap<K, V> subMap(K fromKey, K toKey);
Collections SortedMap<K, V> headMap(K toKey);
Généricité :
introduction
SortedMap<K, V> tailMap(K fromKey);
Effacement de type, K firstKey();
variance, tableaux
Lambda-expressions
K lastKey();
Les “streams” }
Wildcards

Gestion des
erreurs et
exceptions
Implémentation typique : TreeMap.
Interfaces
graphiques

Concurrence
Compléments
en POO Fabriques statiques
Aldric Degorre

Introduction
Fabriques statiques du JDK → alternative intéressante aux consructeurs de collections :
Style
• Nombreuses dans la classe Collections 246 : collections vides, conversion d’un
Objets et
classes type de collection vers un autre, création de vues avec telle ou telle propriété, ...
Types et
polymorphisme • Pour obtenir une liste depuis un tableau : Arrays.asList(tableau).
Héritage
• Dans Java 9, fabriques de List, Set et Map immuables, toutes nommées “of”.
Généricité
Collections Exemples :
Généricité :
introduction
Effacement de type,
List<String> semaine = List.of("lundi", "mardi", "mercredi", "jeudi",
variance, tableaux
"vendredi", "samedi", "dimanche");
Lambda-expressions
Les “streams” Map<String, String> instruments = Map.of("guitare", "cordes", "piano", "cordes",
Wildcards "clarinette", "vent");
Gestion des Set<Integer> premiers = Set.of(2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31);
erreurs et
exceptions

Interfaces
graphiques
Appeler une fabrique plutôt qu’un constructeur évite de choisir une implémentation : on
Concurrence fait confiance à la fabrique pour choisir la meilleure pour les paramètres donnés.
246. Noter le ’s’
Compléments
en POO Collections et tableaux
Aldric Degorre Quand les utiliser

Introduction

Style Avantages des tableaux : syntaxe légère et efficacité.


Objets et
classes Avantages des collections génériques :
Types et
polymorphisme • polyvalence (plein de collections adaptées à des cas différents)
Héritage
• polymorphisme via les interfaces de collections
Généricité
Collections
Généricité : • sûreté du typage (“vraie” généricité)
introduction
Effacement de type,
variance, tableaux
Lambda-expressions
Conclusion, utilisez les collections, sauf :
Les “streams”
Wildcards
• si vous prototypez un programme très rapidement et vous appréciez la simplicité
Gestion des
erreurs et
exceptions • si vous souhaitez optimiser la performance au maximum 247 248
Interfaces
graphiques
247. Cela dit, les méthodes du collection framework, sont écrites et optimisées par des experts et déjà
Concurrence
testées par des milliers de programmeurs. Pensez-vous faire mieux ? (peut-être, si besoin très spécifique)
Discussion 248. Mais pourquoi programmez-vous en Java alors ?
Compléments
en POO Types génériques
Aldric Degorre
Exemple (de l’API) :
Introduction

Style public interface Comparator<T> {


public int compare(T o1, T o2);
Objets et
classes public boolean equals(Object o);
}
Types et
polymorphisme

Héritage
→ Que veut dire ce “<T>” ?
Généricité
Collections
→ Comparator est une interface générique : un type paramétré par un autre 249
Généricité :
introduction

Types génériques de l’API :


Effacement de type,
variance, tableaux
Lambda-expressions
Les “streams”
Wildcards • Les collections de Java ≥ 5 (interfaces et classes génériques).
Gestion des
erreurs et Ce fait seul suffit à justifier l’intérêt des génériques et leur introduction dans Java 5.
exceptions

Interfaces • Les interfaces fonctionnelles de Java ≥ 8 (pour les lambda expressions).


graphiques

Concurrence
• Plein d’autres... (Stream, Future, CompletableFuture, ForkJoinTask, ...)
249. Ou “constructeur de type”. Mais cette terminologie est rarement utilisée en Java.
Compléments
en POO Types génériques
Aldric Degorre Pourquoi ? (1)

Introduction

Style La généricité est un procédé permettant d’augmenter la réutilisabilité du code de façon


Objets et maîtrisée 250 : grâce à des relations fines entre les types utilisés.
classes

Types et
polymorphisme
Sur un exemple :
Héritage

Généricité class Boite { // non polymorphe


Collections public int x;
Généricité :
introduction void echange(Boite autre) {
Effacement de type, int ech = x; x = autre.x; autre.x = ech;
variance, tableaux
Lambda-expressions }
Les “streams” }
Wildcards

Gestion des
erreurs et
exceptions
Inconvénient : définition qui ne marche que pour les boîtes à entiers.
Interfaces
graphiques
Réutilisabilité : zéro !
Concurrence
250. Par opposition au polymorphisme par sous-typage, où, par exemple, pour les arguments d’appel de
Analyse méthode, tout sous-type fait l’affaire indépendamment des autres types utilisés en argument.
Compléments
en POO Types génériques
Aldric Degorre Pourquoi ? (2)

Introduction

Style

Objets et Première solution : boîte universelle (polymorphisme par sous-typage)


classes
class Boite { // très (trop ?) polymorphe
Types et
polymorphisme public Object x; // contient des Object, supertype de tous les objets
void echange(Boite autre) {
Héritage
Object ech = x; x = autre.x; autre.x = ech;
Généricité }
Collections
Généricité :
}
introduction
Effacement de type,
variance, tableaux
Lambda-expressions
Les “streams”
Réutilisabilité : semble totale (on peut tout mettre dans la boîte).
Inconvénient : on ne sait pas (avant l’exécution 251 ) quel type contient une telle boîte →
Wildcards

Gestion des
erreurs et
exceptions
difficile d’utiliser la valeur stockée (il faut tester et caster).
Interfaces
graphiques

Concurrence
251. En fait, programmer des classes comme cette version de Boite revient à abandonner le bénéfice du
Analyse typage statique (pourtant une des forces de Java).
Compléments
en POO Types génériques
Aldric Degorre Pourquoi ? (3)

Introduction

Style

Objets et
classes Cas d’utilisation problématique :
Types et
polymorphisme Boite b1 = new Boite(), b2 = new Boite(); b1.x = 6; b2.x = "toto";
System.out.println(7 * (Integer) b1.x); // <- là c'est ok
Héritage
b1.echange(b2);
Généricité System.out.println(7 * (Integer) b1.x); // <- ClassCastException !!
Collections
Généricité :
introduction
Effacement de type,
variance, tableaux En fait on aurait dû tester le type à l’exécution :
Lambda-expressions
Les “streams”
Wildcards
if (b.x instanceof Integer) System.out.println(7 * (Integer) b.x);

Gestion des
erreurs et
exceptions ... mais on préfèrerait vraiment que le code soit garanti par le compilateur.
Interfaces
graphiques

Concurrence
Analyse
Compléments
en POO Types génériques
Aldric Degorre Pourquoi ? (4)

Introduction La bonne solution : boîte générique (→ polymorphisme générique)


Style
class Boite<C> {
Objets et public C x;
classes
void echange(Boite<C> autre) { C ech = x; x = autre.x; autre.x = ech; }
Types et }
polymorphisme
... // plus loin :
Héritage Boite<Integer> b1 = new Boite<>(); Boite<String> b2 = new Boite<>();
Généricité b1.x = 6; b2.x = "toto";
Collections System.out.println(7 * b1.x); // <- là c'est toujours ok (et sans cast, SVP !)
Généricité :
introduction // b1.echange(b2); // <- ici erreur à la compilation ! (ouf !)
Effacement de type, System.out.println(7 * b1.x);
variance, tableaux
Lambda-expressions
Les “streams”
Wildcards
• La généricité permet des vérifications de type très précises dès de la
Gestion des
erreurs et compilation 252 .
exceptions
• C’est en fait uniquement cela car la valeur prise concrètement par le paramètre de
Interfaces
graphiques type est oubliée dès qu’elle est vérifiée 253 (effacement de type / type erasure).
Concurrence
252. Or le plus tôt on détecte une erreur, le mieux c’est !
Analyse 253. Conséquence : les objets de classe générique ne savent pas avec quel paramètre ils ont été instanciés.
Compléments
en POO Types génériques
Aldric Degorre Usage de base (1)

Introduction

Style • Type générique : type (classe ou interface) dont la définition fait intervenir un
Objets et
classes
paramètre de type (dans les exemples, c’était T et C).
Types et • Le paramètre introduit dans l’en-tête de la définition du type peut ensuite être utilisé
polymorphisme

Héritage
dans son corps 254 comme si c’était un vrai nom de type.
Généricité class Triplet<T,U,V> { // introduction de T, U et V
Collections
T elt1; U elt2; V elt3; // utilisation de T, U et V
Généricité :
introduction public Triplet(T e1, U e2, V e3) { elt1 = e1; elt2 = e2; elt3 = e3; }
Effacement de type,
variance, tableaux
}
Lambda-expressions
Les “streams”
Wildcards
• ... comme un vrai nom de type, mais on ne sait rien de lui 255 . En particulier, on ne
Gestion des
erreurs et connait pas de constructeur, aucun moyen d’instancier T !
exceptions

Interfaces 254. Seulement en contexte non statique : en effet, le paramètre symbolise le type qui serait fixé lors de
graphiques
l’instanciation ⇒ pas de signification dans contexte statique.
Concurrence
“serait” : parce qu’à l’exécution, tout cela est en réalité oublié.
255. Éventuellement un supertype si paramètre borné. Dans ce cas, on connait ses membres hérités.
Compléments
en POO Types génériques
Aldric Degorre Usage de base (2)

Introduction
• À l’usage du type générique, il faut remplacer le paramètre par un type concret.
Style

Objets et On obtient un type paramétré (exemple : List est un type générique,


classes
List<String> un des types paramétrés que List permet de construire 256 )
Types et
polymorphisme
• Utilisation de classe générique par instanciation directe :
Héritage

Généricité // Java 5 et plus :


Collections Triplet<String, String, Integer> t1 =
Généricité :
introduction
new Triplet<String, String, Integer>("Marcel", "Durand", 23);
Effacement de type, // Java 7 et plus :
variance, tableaux
Lambda-expressions
Triplet<String, String, Integer> t2 = new Triplet<>("Marcel", "Durand", 23);
Les “streams”
Wildcards

Gestion des • Utilisation de classe générique par extension non générique (spécialisation) :
erreurs et
exceptions class TroisEntiers extends Triplet<Integer, Integer, Integer> {
Interfaces public TroisEntiers(Integer x, Integer y, Integer z) { super(x,y,z); }
graphiques }
Concurrence

256. Dans certaines terminologies, les types génériques sont appelés “constructeurs de type”.
Compléments
en POO Types génériques
Aldric Degorre Usage de base (3)

Introduction

Style

Objets et
classes
• Le type concret substituant le paramètre doit être un type référence :
Types et
polymorphisme Triplet<int, boolean, char> est interdit 257 !
Héritage
• Un nom de type générique seul, sans paramètre (comme “Triplet”), est aussi un
Généricité
Collections type légal.
Généricité :
introduction
Effacement de type,
On appelle cela un type brut (raw type).
variance, tableaux
Lambda-expressions Son utilisation est fortement déconseillée, mais elle est permise pour assurer la
compatibilité ascendante 258 .
Les “streams”
Wildcards

Gestion des
erreurs et
exceptions

Interfaces
graphiques

Concurrence
257. Pour l’instant. Il semble qu’il soit prévu de permettre cela dans une prochaine version de Java.
258. Un source Java < 5 compile avec javac ≥ 5. Or certains types sont devenus génériques entre temps.
Compléments
en POO Parallèle entre type générique et méthode
Aldric Degorre

Introduction La déclaration et l’utilisation des types génériques rappellent celles des méthodes.
Style
Similitudes :
Objets et
classes
• introduction des paramètres (de type ou de valeur) dans l’en-tête de la déclaration ;
Types et
polymorphisme
• utilisation des noms des paramètres dans le corps de la déclaration seulement ;
Héritage

Généricité • pour utiliser le type générique ou appeler la méthode, on passe des valeurs
concrètes pour remplacer les paramètres.
Collections
Généricité :
introduction
Effacement de type,
variance, tableaux
Lambda-expressions Principales différences :
Les “streams”

• Les paramètres des génériques représentent des types alors que ceux des
Wildcards

Gestion des
erreurs et
exceptions
méthodes représentent des valeurs.
Interfaces • Le “remplacement” a lieu à la compilation seulement pour les paramètres de type.
graphiques

Concurrence
Pour les paramètres des méthodes, remplacement par une valeur à l’exécution.
Discussion
Compléments
en POO Bornes de paramètres de type (1)
Aldric Degorre

Introduction • Pour limiter les concrétisations autorisées, un paramètre de type admet des bornes
Style supérieures 259 (se comportant comme supertypes du paramètre) :
Objets et
classes class Calculator<Data extends Number>
Types et
polymorphisme
Ici, Data devra être concrétisé par un sous-type de Number : une instance de
Héritage
Calculator travaillera sur nécessairement avec un certain sous-type de Number,
Généricité
Collections celui choisi à son instanciation.
Généricité :

• Pour définir des bornes supérieures multiples (p. ex. pour implémenter de multiples
introduction
Effacement de type,
variance, tableaux
Lambda-expressions
Les “streams”
interfaces), les supertypes sont séparés par le symbole “&” :
Wildcards
class RunWithPriorityList<T extends Comparable<T> & Runnable> implements List<T>
Gestion des
erreurs et
exceptions
Ainsi, T est défini comme inclus dans l’intersection de Comparable<T> et de
Interfaces
graphiques Runnable.
Concurrence
259. On verra dans la suite que les bornes inférieures existes aussi, mais elles ne s’appliquent qu’aux
wildcards (et non aux paramètres de type).
Compléments
en POO Bornes de paramètres de type (2)
Aldric Degorre

Introduction

Style

Objets et
classes

Types et
polymorphisme On peut prolonger l’analogie avec les méthodes et leurs paramètres :
Héritage déclaration méthode déclaration type générique
Généricité
Collections
domaine type du paramètre formel ensemble des sous-types de la borne supérieure
Généricité :
introduction du dans la signature de la donnée lors de son introduction
paramètre méthode (obligatoire) (optionelle)
Effacement de type,
variance, tableaux
Lambda-expressions
Les “streams”
Wildcards

Gestion des
erreurs et
exceptions

Interfaces
graphiques

Concurrence

Discussion
Compléments
en POO Déclaration de paramètre de type niveau méthode
Aldric Degorre

Introduction • Il est aussi possible d’introduire un paramètre de type dans la signature d’une
Style méthode (dans une classe pas forcément générique) :
Objets et
classes static <E> List<E> inverseListe(List<E> l) { ... ; E x = get(0); ...; }
Types et
polymorphisme

Héritage
• Dans l’exemple ci-dessus, on garantit que la liste retournée par inverseListe()
Généricité a le même type d’éléments que celle donnée en paramètre.
Collections
Généricité :
introduction
• Usages possibles :
Effacement de type,
variance, tableaux • contraindre plusieurs types apparaissant dans la signature de la méthode à être les
mêmes... sans pour autant dire quel type concret ce sera !
Lambda-expressions
Les “streams”
Wildcards
• introduire localement un nom de type utilisable dans le corps de la méthode (combiné
Gestion des
erreurs et
avec les bornes multiples, cela permet de définir des types intersection :
exceptions <I extends A & B> void f(List<I> l){...}).
Interfaces
graphiques

Concurrence
NB : il est donc, malgré tout 260 , possible d’écrire une méthode statique générique.
260. Rappel : les paramètres de type introduits pour la classe n’existent qu’en contexte non statique.
Compléments
en POO Effacement de type (type erasure)
Aldric Degorre Explication

Introduction
• À l’exécution, les valeurs concrètes des paramètres de type sont inconnues : c’est
Style

Objets et
l’effacement de type (type erasure).
classes

Types et
• Dans un objet, 1 seule information de typage disponible : sa classe 261 .
polymorphisme
Il n’y a pas d’“objets génériques”. Une instance de type paramétré ne contient pas
Héritage
de référence vers les types des paramètres 262 .
Généricité
Collections
Généricité :
introduction Valeur du paramètre connue ni à l’exécution, ni à la compilation 263 → alors à quoi sert-il ?
Effacement de type,

→ Un paramètre de type est juste un symbole formel permettant d’exprimer des énoncés
variance, tableaux
Lambda-expressions

logiques que le compilateur doit prouver avant d’accepter le programme.


Les “streams”
Wildcards

Gestion des
erreurs et Ceux-ci sont de la forme : ∀T [(∀B, ∈ BornesSup(T), T <: B) ⇒ BienType(progGen(T))] .
exceptions

Interfaces 261. Chaque objet contient une référence vers sa classe.


graphiques
262. Par ailleurs, une classe générique n’a qu’une seule représentation en mémoire, valant pour tous ses
Concurrence
types paramétrés. Elle ne contient donc pas non plus les valeurs concrètes des paramètres.
263. Les objets ne sont pas encore instanciés.
Compléments
en POO Effacement de type (type erasure)
Aldric Degorre Conséquences

Introduction

Style

Objets et
classes À l’exécution, il est impossible de savoir comment un paramètre de type a été concrétisé.
Types et
polymorphisme En particulier :
Héritage

Généricité
• Faute d’être raisonnablement exécutables, ces expressions ne compilent pas :
Collections
Généricité :
introduction
• x instanceof P (P paramètre de type) ;
Effacement de type,
variance, tableaux
• x instanceof TypeG<Y> 264 (Y type quelconque).
Lambda-expressions
Les “streams”
Wildcards • On ne peut pas déclarer d’exception générique Ex<T> car catch(Ex<X> ex) ne
Gestion des
erreurs et
serait pas non plus évaluable (même problème qu’instanceof).
exceptions

Interfaces
graphiques

Concurrence

264. Mais x instanceof TypeG compile, c’est un des rares cas où on tolère le raw type.
Compléments
en POO Types génériques et sous-typage
Aldric Degorre Invariance des génériques

Introduction Le problème : examinons l’exemple suivant (qui ne compile pas).


Style
public static void main(String[] args) {
Objets et
List<VoitureSansPermis> listVoitureSP = new ArrayList<>();
classes

Types et // l1: ceci est en fait interdit... mais supposons que ça passe...
polymorphisme
List<Voiture> listVoiture = listVoitureSP;
Héritage

Généricité // l2: instruction bien typée (pour le compilateur), mais....


Collections listVoiture.add(new Voiture());
Généricité :
introduction
Effacement de type,
variance, tableaux
// l3: ... logiquement ça afficherait "Voiture" (contradictoire)
Lambda-expressions System.out.println(listVoitureSP.get(0).getClass());
Les “streams”
}
Wildcards

Gestion des
erreurs et
exceptions
S’il compilait, en l’exécutant, à la fin, listVoitureSP = listVoiture contiendrait
Interfaces des Voiture → contredit la déclaration de listVoitureSP !
graphiques

Concurrence Ainsi, Java interdit l1 : deux spécialisations différentes du même type générique sont
incompatibles. Ont dit que les génériques de Java sont invariants.
Compléments
en POO Types génériques et sous-typage
Aldric Degorre Invariance des génériques

Introduction

Style type erasure → vérification à la compilation seulement. Court-circuitons-la, pour voir :


Objets et // l1': version avec "triche" (pas d'exception car type erasure)
classes
List<Voiture> listVoiture = (List<Voiture>)(Object) listVoitureSP;
Types et
polymorphisme
/* l2: */ listVoiture.add(new Voiture());
Héritage

Généricité // l3: ça affiche effectivement "Voiture" (oooh !)


Collections System.out.println(listVoitureSP.get(0).getClass());
Généricité :
introduction
Effacement de type, // l4: et pour la forme, une petite ClassCastException :
variance, tableaux
Lambda-expressions VoitureSansPermis vsp = listVoitureSP.get(0);
Les “streams”
Wildcards

Gestion des Note : cependant le compilateur détecte la conversion “louche” et signale un


erreurs et
exceptions avertissement (warning) “unchecked conversion” pour la ligne l1'.
Interfaces
graphiques Moralité : si avertissement, alors garanties usuelles supprimées.
Concurrence ClassCastException peut se produire à l’exécution.
Compléments
en POO Tableaux – génériques ou pas ? (1)
Aldric Degorre

Introduction Remarque : l’analogue à l’exemple précédent utilisant Voiture[] au lieu de


Style List<Voiture> compile sans avertissement :
Objets et
classes public static void main(String[] args) {
VoitureSansPermis[] listVoitureSP = new VoitureSansPermis[100];
Types et
polymorphisme
// l1: ceci est autorisé !
Héritage
Voiture[] listVoiture = listVoitureSP;
Généricité
Collections
Généricité :
// l2: instruction bien typée (pour le compilateur), mais.... ArrayStoreException !
introduction
listVoiture[0] = new Voiture();
Effacement de type,
variance, tableaux
Lambda-expressions
// l3: on ne va pas jusque là
Les “streams”
Wildcards System.out.println(listVoitureSP[0].getClass());
}
Gestion des
erreurs et
exceptions

Interfaces → Les tableaux sont covariants (l1 autorisé) : [A <: B] =⇒ [A[] <: B[]].
graphiques

Concurrence Mais on crashe plus loin, lors de l’exécution de l2.


Compléments
en POO Tableaux – génériques ou pas ? (2)
Aldric Degorre

Introduction

Style
• covariance à la place d’invariance : ⇒ vérifications moins strictes à la compilation,
Objets et
classes rendant possibles des problèmes à l’exécution 265
Types et
polymorphisme • pour détecter les problèmes au plus tôt : à l’instanciation, un tableau enregistre le
Héritage nom du type déclaré pour ses éléments (pas d’effacement de type)
Généricité
Collections
• cela permet à la JVM de déclencher ArrayStoreException lors de toute
Généricité :
introduction tentative d’y stocker un élément du mauvais type, au lieu de
ClassCastException lors de son utilisation (donc bien plus tard).
Effacement de type,
variance, tableaux
Lambda-expressions
Les “streams”

→ “Genre de” généricité, mais conception obsolète : avec la généricité moderne, la


Wildcards

Gestion des
erreurs et compilation garantit une exécution sans erreur.
exceptions

Interfaces
graphiques

Concurrence
265. Raison : un tableau est à la fois producteur et consommateur. D’un point de vue théorique, une telle
structure de données ne peut être qu’invariante, si on veut des garanties dès la compilation.
Compléments
en POO Génériques et tableaux
Aldric Degorre Différence de philosophie (1)

Introduction

Style

Objets et • Tableaux : (vérification à l’exécution, mais le + tôt possible)


classes

Types et
• usage normal : conversion sans warning de SousType[] à SuperType[] par
polymorphisme upcasting (implicite).
Héritage Possibilité d’ArrayStoreException à l’exécution.
Généricité
Collections Object[] tab = new String[10];
Généricité :
introduction
tab[0] = Integer.valueOf(3); // BOOM ! (ArrayStoreException)
Effacement de type,
variance, tableaux
Lambda-expressions Pas idéal, mais aurait pu être pire : le crash évite que le programme continue avec une
Les “streams”
Wildcards mémoire incohérente.
Gestion des
• usage anormal, avec cast explicite vers type incompatible :
erreurs et
exceptions
(String[])(Object)(new Integer[10]) compile mais avec warning et fait
Interfaces
ClassCastException quand on exécute (tout va bien : on avait été prévenu).
graphiques

Concurrence

Résumé
Compléments
en POO Génériques et tableaux
Aldric Degorre Différence de philosophie (2)

Introduction

Style

Objets et • Génériques : (vérification à la compilation... puis plus rien)


classes

Types et • usage normal, le compilateur rejette toute tentative de conversion implicite de


polymorphisme
Gen<A> à Gen<B>, garantissant qu’à l’exécution toute instance de Gen<T> sera bien
Héritage
utilisée avec le type T → exécution cohérente et sans exception garantie.
Généricité • usage anormal, conversion forcée : (Gen<B>)(Object)(new Gen<A>())
Collections
Généricité :
introduction
compile avec un warning et... provoque des erreurs à retardement à l’exécution (très
Effacement de type,
variance, tableaux
mal, mais on a été prévenu) ! Exemple :
Lambda-expressions
Les “streams” List<String> ls = new ArrayList<String>();
Wildcards
List<Integer> li = (List<Integer>)(Object) ls; //exécution ok ! (oh !)
Gestion des li.add(5); // toujours pas de crash... (double oh !)
erreurs et ls.get(0)); // BOOM à retardement ! (ClassCastException)
exceptions

Interfaces
graphiques

Concurrence

Résumé
Compléments
en POO Génériques et tableaux
Aldric Degorre Et si on mélangeait ?

Introduction

Style Tableaux et génériques ne font pas bon ménage : les uns ont besoin de tout savoir à
Objets et l’exécution, alors que les autres veulent tout oublier !
classes

Types et • Avec T, paramètre de type, new T[taille] est impossible.


polymorphisme

Héritage Raison : pour instancier un tableau, Java doit connaître dès la compilation le type
Généricité concret des éléments du tableau.
Collections
Généricité :
introduction
Or à la compilation, T n’est pas associé à un type concret.
Effacement de type,
variance, tableaux
Lambda-expressions
• Les types tableau de types paramétrés, comme List<Integer>[], sont illégaux.
Les “streams”
Wildcards
Raison : à l’exécution, Java ne sait pas distinguer List<Integer> et
Gestion des List<String> et donc ne peut pas accepter de mettre des List<Integer>
erreurs et
exceptions dans un tableau sans aussi accepter List<String>.
Interfaces
graphiques
=⇒ Tout ce qui est dans le tableau pouvant ensuite être affecté à une variable de
Concurrence
type List<Integer>, la garantie promise par la généricité serait cassée.
Compléments
en POO Génériques et tableaux
Aldric Degorre Premier interdit : new T[10]

Introduction Supposons T extends Up paramètre de type.


Style

Objets et
new T[10] est aussi interdit.
classes

Types et
Raison : après compilation, T est oublié et remplacé par Up. Au mieux new T[10]
polymorphisme pourrait être compilé comme new Up[10]. Mais si c’était ce qui se passait, on pourrait
Héritage
trop facilement “polluer” la mémoire sans s’en rendre compte :
Généricité
Collections static <T> T[] makeArray(int size) { return new T[10]; /* interdit ! */ }
Généricité :
introduction
static {
Effacement de type, String[] tString = makeArray(10); // à l'exécution on affecterait un Object[]
variance, tableaux
Lambda-expressions Object[] tObject = tString; // toujours autorisé (covariance)
Les “streams” tObject[0] = 42; // et BOOM ! Maintenant tString contient un Integer !
Wildcards
}
Gestion des
erreurs et
exceptions
En pratique, pour faire compiler cela, il faut “tricher” avec cast explicite :
T[] tab = (T[])new Up[10];.
Interfaces
graphiques

Concurrence Ça n’empêche pas le problème ci-dessus, mais au moins le compilateur affiche un


warning (“unchecked conversion”).
Compléments
en POO Génériques et tableaux
Aldric Degorre Deuxième interdit : Gen<T>[] (1)

Introduction Mauvais scenario, pouvant se produire si on autorisait les tableaux de génériques :


Style
class Box<T> {
Objets et final T x;
classes
Box(T x) { this.x = x; }
Types et }
polymorphisme

Héritage class Loophole {


Généricité public static void main(String[] args) {
Collections Box<String>[] bsa = new Box<String>[3]; // supposons que cette ligne compile
Généricité :
introduction Object[] oa = bsa; // autorisé car tableaux covariants
Effacement de type,
variance, tableaux
Lambda-expressions /* autorisé à la compilation (Box < Object) le test à l'exécution est aussi ok
Les “streams” (parce que le tableau référencé par oa est celui instancié à la première ligne
Wildcards
et que le type enregistré dans la JVM est juste Box) */
Gestion des oa[0] = new Box<Integer>(3);
erreurs et
exceptions
/* ... et là , c'est le drame !
Interfaces
graphiques
(ClassCastException, alors que l'instruction est bien typée !) */
String s = bsa[0].x;
Concurrence
}
Exemple }
Compléments
en POO Génériques et tableaux
Aldric Degorre Deuxième interdit : Gen<T>[] (2)

Introduction

Style new Box<Integer>[10] est interdit.


Objets et
classes En effet, le tableau instancié serait de type Box[] 266 , ce qui rendrait possible le
Types et
polymorphisme
scénario du transparent précédent.
Héritage
En revanche, Java autorise new Box[10].
Généricité
Collections
Généricité :
Remarque, du coup, là aussi, il existe une “triche” pour faire compiler l’exemple du
introduction
Effacement de type,
transparent précédent : on remplace new Box<String>[3] par new Box[3].
Le compilateur émet heureusement un warning (“unchecked conversion”)... et à
variance, tableaux
Lambda-expressions

l’exécution, on a effectivement ClassCastException


Les “streams”
Wildcards

Gestion des
erreurs et Encore une fois, la “triche” permet de compiler, n’empêche pas l’exception à l’exécution,
exceptions
mais seulement on a été prévenu par le warning du compilateur !
Interfaces
graphiques

Concurrence
266. À cause de l’effacement de tye, Box<Integer> n’existe pas à l’exécution. Toutes les spécialisations
ont le même type : Box !
Compléments
en POO Fonctions de première classe et fonctions d’ordre supérieur
Aldric Degorre Le besoin (1)

Introduction Appeler 5 fois une méthode f déjà connue :


Style
f(); f(); f(); f(); f();
Objets et
classes

Types et Appeler f un nombre de fois inconnu à l’avance :


polymorphisme
public static void repeatF(int n) { for (int i = 0; i < n; i++) f(); }
Héritage

Généricité
Collections
Généricité :
Appeler 5 fois une méthode inconnue à l’avance ?
introduction
Effacement de type, // tentative
variance, tableaux
Lambda-expressions
public static void repeat5(??? f) { // quel type pour f ?
Les “streams” for (int i = 0; i < 5; i++) f(); // si f une variable, "f()" -> erreur de syntaxe
Wildcards }
Gestion des
erreurs et
exceptions
• repeat5 = fonction avec paramètre de type fonction : c’est une fonction d’ordre
Interfaces
graphiques supérieur (FOS).
Concurrence
• Pour que cela existe, il faut un langage qui autorise le passage de fonction en
Analyse
paramètre : des fonctions de première classe (FPC).
Compléments
en POO Fonctions de première classe et fonctions d’ordre supérieur
Aldric Degorre Le besoin (2)

Introduction

Style
• Une FPC peut être affectée à une variable, être le paramètre d’une FOS, ou bien sa
Objets et
classes valeur de retour : c’est une valeur comme une autre.
Types et
polymorphisme
• Avec des valeurs fonction, il devient possible de manipuler des instructions sans
Héritage les exécuter/évaluer immédiatement (évaluation paresseuse). Elles peuvent ainsi :
Généricité • être transformées, composées avant d’être évaluées ;
Collections
Généricité : • être évaluées plus tard , une, plusieurs fois ou pas du tout, en fonction de critères
introduction
Effacement de type,
variance, tableaux
programmables ; (condition, répétition, déclenchement par évènement ultérieur, ...) ;
Lambda-expressions • exécutées dans un autre contexte (p. ex. autre thread 267 ).
Les “streams”
Wildcards
De telles modalités d’exécution sont programmables en tant que FOS qui se
Gestion des
erreurs et comportent, en gros, comme de nouveaux blocs de contrôle.
exceptions

Interfaces
graphiques
267. Voir chapitre programmation concurrente.
Concurrence
La programmation concurrente a probablement été un argument primordial pour l’introduction des
lambda-expressions en Java.
Compléments
en POO Fonctions de première classe et fonctions d’ordre supérieur
Aldric Degorre Plus d’exemples (1)

Introduction

Style

Objets et
classes

Types et Bloc if/else :


polymorphisme
// types et syntaxe d'appel des FPC toujours fantaisistes dans cet exemple
Héritage
public static void ifElse(??? condition, ??? ifBlock, ??? elseBlock) {
Généricité if (condition()) ifBlock();
Collections
Généricité :
else elseBlock();
introduction }
Effacement de type,
variance, tableaux
Lambda-expressions
Les “streams”
Wildcards
Impossible d’écrire la signature d’une méthode mimant le bloc if/else sans
Gestion des paramètres FPC. Une telle méthode est nécessairement une FOS.
erreurs et
exceptions

Interfaces
graphiques

Concurrence
Exemple
Compléments
en POO Fonctions de première classe et fonctions d’ordre supérieur
Aldric Degorre Plus d’exemples (2)

Introduction

Style

Objets et
classes
Évidemment, plus intéressant d’écrire de nouveaux blocs de contrôle, par exemple :
Types et
polymorphisme Bloc retry :
Héritage
// pareil, ne faites pas ça à la maison !
Généricité
Collections
public static void retry(??? instructions, int tries) {
Généricité : while (tries > 0) {
introduction
Effacement de type, try { instructions(); return; }
variance, tableaux
catch (Throwable t) { tries--; }
Lambda-expressions
Les “streams” }
Wildcards throw new RuntimeException("Failure␣persisted␣after␣all␣tries.");
Gestion des }
erreurs et
exceptions

Interfaces
graphiques

Concurrence
Exemple
Compléments
en POO Fonctions de première classe et fonctions d’ordre supérieur
Aldric Degorre Plus d’exemples (3)

Introduction Encore un exemple :


Style
// toujours en syntaxe fantaisiste
Objets et
// SURTOUT NE PAS RECOPIER OU MÊME RETENIR !
classes
public static <U, V> List<V> map(List<U> l, ??? f) {
Types et List<V> ret = new ArrayList<>();
polymorphisme
for (U x : l) ret.add(f(x));
Héritage return ret;
Généricité }
Collections
Généricité :

(L’API Stream de Java 8 contient plein de méthodes dans ce genre.)


introduction
Effacement de type,
variance, tableaux

Ou encore :
Lambda-expressions
Les “streams”
Wildcards

Gestion des public static readPacket(Socket s, ??? handler) {


erreurs et ...
exceptions }
Interfaces
graphiques

Concurrence Où handler est une FPC qui traitera les données du prochain paquet reçu sur le socket
Exemple réseau s (on parle de fonction de rappel ou callback).
Compléments
en POO Programmation fonctionnelle
Aldric Degorre

Introduction
Concepts de FPC et de FOS essentiels pour la programmation fonctionnelle (PF) :
Style

Objets et • PF = paradigme de programmation, au même titre que la POO.


classes

Types et • Idée de base : on conçoit un programme comme une fonction mathématique,


polymorphisme
elle-même obtenue par composition d’un certain nombre d’autres fonctions.
Héritage

Généricité
Or pour pouvoir composer les fonctions, il faut supporter les FOS et donc les FPC.
Collections
Généricité :
introduction
• Langages fonctionnels connus : Lisp (et variantes : Scheme, Emacs Lisp, Clojure... ),
Effacement de type,
variance, tableaux ML (et variantes : OCaml, F#... ), Haskell, Erlang...
Lambda-expressions
Les “streams”
Wildcards
• Rien n’empêche d’être à la fois objet et fonctionnel (Javascript, Scala, OCaml,
Gestion des Common Lisp ...)
erreurs et

Java (langage OO) acquiert des concepts fonctionnels avec sa version 8. 268
exceptions

Interfaces
graphiques

Concurrence
268. Mais ne devient pas un langage de PF pour autant (on verra plusieurs raisons). Remarque : quasiment
tous les langages modernes supportent les FPC, bien qu’ils ne soient pas tous des LPF.
Compléments
en POO Que faut-il pour qu’un langage supporte les FPC ?
Aldric Degorre

Introduction

Style

Objets et Principalement 3 choses :


classes

Types et • un système de types capable de décrire les types des FPC


polymorphisme

Héritage • une syntaxe adaptée, permettant d’écrire des expressions à valeur dans ces types.
Généricité
Collections
Notamment, il faut des littéraux fonctionnels (appelés aussi
Généricité :
introduction lambda-expressions 269 ou encore fonctions anonymes).
Effacement de type,
variance, tableaux
Lambda-expressions
• une représentation en mémoire adaptée pour les FPC
Les “streams”
Wildcards (en Java, forcément des objets particuliers)
Gestion des
erreurs et
exceptions

Interfaces
graphiques

Concurrence
269. En particulier en Java. C’est donc le nom “lambda-expression” que nous allons utiliser.
Analyse Pourquoi “lambda” ? Référence au lambda-calcul d’Alonzo Church : la fonction x 7→ f(x) s’y écrit λx.f(x).
Compléments
en POO FPC avant Java 8
Aldric Degorre Analyse de l’existant

Introduction Depuis toujours, Java permet de décrire une fonction à l’aide d’un objet :
Style

Objets et • il suffit de créer une classe qui a cette fonction comme méthode
classes

Types et
• et de créer une instance unique qui jouera le rôle de valeur fonctionnelle.
polymorphisme

Héritage Au mieux, on utilise les classes anonymes. Typiquement, pour créer un thread :
Généricité new Thread( /* début de l'expression-fonction */ new Runnable {
Collections
Généricité : @Override public void run() { /* choses à faire dans l'autre thread */ }
introduction
} /* fin de l'expression-fonction */ ).start()
Effacement de type,
variance, tableaux
Lambda-expressions
Les “streams”
Wildcards
Ici, on passe une fonction (décrite par la méthod run) au constructeur de Thread.
Gestion des
erreurs et Inconvénients :
exceptions

Interfaces • syntaxe lourde et peu lisible, même avec classes anonymes,


graphiques

Concurrence
• obligation de se rappeler et d’écrire des informations sans rapport avec la fonction
Analyse qu’on décrit (nom de l’interface : Runnable ; et de la méthode implémentée : run).
Compléments
en POO FPC avant Java 8
Aldric Degorre Bilan

Introduction

Style

Objets et
classes

Types et
polymorphisme Bilan pour les FPC avant Java 8 :
Héritage

Généricité
• typage : au cas par cas, rien de prévu, pas de standard : chaque méthode peut
Collections
Généricité :
spécifier une interface différente pour la fonction passée en argument.
introduction
Effacement de type,
variance, tableaux
• syntaxe : lourde et peu pratique.
Lambda-expressions
Les “streams” • représentation en mémoire : instance d’une classe contenant juste une méthode.
Wildcards

Gestion des
erreurs et
exceptions

Interfaces
graphiques

Concurrence
Compléments
en POO FPC avant Java 8
Aldric Degorre Bilan

Introduction

Style

Objets et
classes Bilan pour les FPC avant Java 8 :
Types et
polymorphisme • typage : au cas par cas, rien de prévu, pas de standard : chaque méthode peut
Héritage
spécifier une interface différente pour la fonction passée en argument.
Généricité
Collections → Java 8 fournit les interfaces fonctionnelles standard du package
java.util.function.
Généricité :
introduction
Effacement de type,
variance, tableaux
Lambda-expressions
Sinon, rien n’est changé au système de type de Java (même sa syntaxe).
Les “streams”
Wildcards • syntaxe : lourde et peu pratique.
Gestion des
erreurs et • représentation en mémoire : instance d’une classe contenant juste une méthode.
exceptions

Interfaces
graphiques

Concurrence
Compléments
en POO FPC avant Java 8
Aldric Degorre Bilan

Introduction

Style

Objets et Bilan pour les FPC avant Java 8 :


classes

Types et • typage : au cas par cas, rien de prévu, pas de standard : chaque méthode peut
polymorphisme
spécifier une interface différente pour la fonction passée en argument.
Héritage

Généricité
→ Java 8 fournit les interfaces fonctionnelles standard du package
Collections java.util.function.
Généricité :

Sinon, rien n’est changé au système de type de Java (même sa syntaxe).


introduction
Effacement de type,
variance, tableaux
Lambda-expressions
Les “streams”
• syntaxe : lourde et peu pratique.
Wildcards
→ Dans Java 8 : lambda-expressions.
Gestion des
erreurs et Type d’une lambda-expression inféré depuis le contexte.
exceptions

Interfaces • représentation en mémoire : instance d’une classe contenant juste une méthode.
graphiques

Concurrence
Compléments
en POO FPC avant Java 8
Aldric Degorre Bilan

Introduction

Style Bilan pour les FPC avant Java 8 :


Objets et
classes • typage : au cas par cas, rien de prévu, pas de standard : chaque méthode peut
Types et
polymorphisme
spécifier une interface différente pour la fonction passée en argument.
Héritage
→ Java 8 fournit les interfaces fonctionnelles standard du package
Généricité java.util.function.
Sinon, rien n’est changé au système de type de Java (même sa syntaxe).
Collections
Généricité :
introduction
Effacement de type,
variance, tableaux • syntaxe : lourde et peu pratique.
Lambda-expressions
Les “streams” → Dans Java 8 : lambda-expressions.
Wildcards

Gestion des
Type d’une lambda-expression inféré depuis le contexte.
erreurs et
exceptions • représentation en mémoire : instance d’une classe contenant juste une méthode.
Interfaces
graphiques
→ Dans Java 8 : on continue comme ça. Les FPC sont les instances des interfaces
Concurrence
fonctionnelles.
Compléments
en POO Interfaces fonctionnelles et types SAM
Aldric Degorre

Introduction

Style

Objets et
classes • Interface fonctionnelle = interface avec 1 seule méthode abstraite 270
Types et
polymorphisme • type SAM : type défini par une interface fonctionnelle (SAM = single abstract
Héritage method).
Généricité
Collections • En fait, il en existe déjà plein avant Java 8 (ex. : interfaces Comparable,
Généricité :
introduction
Effacement de type,
Comparator, Runnable, Callable, ActionListener... ).
variance, tableaux
Lambda-expressions
Les “streams”
• Pour associer des types standard aux FPC, l’API Java 8 en ajoute quelques dizaines,
Wildcards
cf. les 2 pages suivantes.
Gestion des
erreurs et
exceptions

Interfaces
graphiques

Concurrence

270. ... mais autant de méthodes static, default ou private (Java 9) que l’on souhaite !
Compléments
en POO Catalogue des interfaces fonctionnelles de Java 8
Aldric Degorre dans le package java.util.function (1)

Introduction
java.util.function contient toute une série d’interfaces fonctionnelles standard.
Style

Objets et Interfaces génériques :


classes
Interface Type représenté Méthode unique
Types et
polymorphisme BiConsumer<T,U> T × U → {()} void accept(T, U)
Héritage BiFunction<T,U,R> T×U→R R apply(T, U)
Généricité
Collections
BinaryOperator<T> T×T→T T apply(T, T)
Généricité :
introduction
BiPredicate<T,U> T × U → {⊥, ⊤} boolean test(T, U)
Effacement de type,
variance, tableaux Consumer<T> T → {()} void accept(T)
T→R
Lambda-expressions
Les “streams” Function<T,R> R apply(T)
T → {⊥, ⊤}
Wildcards
Predicate<T> boolean test(T)
Gestion des
erreurs et Supplier<T> {()} → T T get()
exceptions
UnaryOperator<T> T→T T apply(T)
Interfaces
graphiques
De plus, ce package contient aussi des interfaces pour les fonctions prenant ou
Concurrence
retournant des types primitifs int, long, double ou boolean (page suivante).
Détails
Compléments
en POO Catalogue des interfaces fonctionnelles de Java 8
Aldric Degorre dans le package java.util.function (2)

Introduction

Style
Interfaces spécialisées :
Objets et
Interface Type représenté Méthode unique
classes BooleanSupplier {()} → {⊥, ⊤} boolean getAsBoolean()
Types et
polymorphisme DoubleBinaryOperator R×R→R double applyAsDouble(double, d
Héritage DoubleConsumer R → {()} void accept(double)
Généricité DoubleFunction<R> R→R R apply(double)
R → {⊥, ⊤}
Collections
Généricité : DoublePredicate void test(double)
introduction
Effacement de type,
variance, tableaux
DoubleSupplier {()} → R double getAsDouble()
Lambda-expressions
Les “streams”
DoubleToIntFunction R→Z int applyAsInt(double)
Wildcards
... ... ...
Gestion des
erreurs et
exceptions
Cf. javadoc de java.util.function pour liste complète.
Interfaces
graphiques
Intérêt des interfaces spécialisées : programmes mieux optimisés 271 qu’avec les types
Concurrence
“emballés” (Int, Long, ...).
Détails 271. Moins d’allocations et d’indirections.
Compléments
en POO Catalogue des interfaces fonctionnelles de Java 8
Aldric Degorre dans le package java.util.function... mais pas seulement !

Introduction

Style Attention, catalogue incomplet :


Objets et
classes
• L’interface java.lang.Runnable reste le standard pour les “fonctions” 272 de
Types et
polymorphisme {()} → {()} (void vers void).
Héritage
• Pas d’interfaces standard pour les fonctions à plus de 2 paramètres → il faut
Généricité
Collections définir les interfaces soi-même :
Généricité :
introduction
Effacement de type, @FunctionalInterface public interface TriConsumer<T, U, V> {
variance, tableaux
void apply(T t, U u, V v);
Lambda-expressions
Les “streams” }
Wildcards

Gestion des
erreurs et L’annotation facultative @FunctionalInterface demande au compilateur de signaler
exceptions
une erreur si ce qui suit n’est pas une définition d’interface fonctionnelle.
Interfaces
graphiques

Concurrence
272. Ces fonctions sont intéressantes pour leurs effets de bord et non pour la transformation qu’elles
Détails représentent. En effet, en mathématiques, card({()} → {()}) = 1.
Compléments
en POO Retour sur les exemples...
Aldric Degorre ... avec les bons types et la bonne syntaxe

Introduction public static void repeat5(Runnable f) { for (int i = 0; i < 5; i++) f.run(); }
Style
public static void ifElse(BooleanSupplier cond, Runnable ifBlock, Runnable elseBlock) {
Objets et
classes if (cond.getAsBoolean()) ifBlock.run();
else elseBlock.run();
Types et
polymorphisme }
Héritage
public static void retry(Runnable instructions, int tries) {
Généricité
Collections while (tries > 0) {
Généricité : try { instructions.run(); return; }
introduction
Effacement de type, catch (Throwable t) { tries--; }
variance, tableaux
Lambda-expressions
}
Les “streams” throw new RuntimeException("Failure␣persisted␣after␣all␣tries.");
Wildcards }
Gestion des
erreurs et
exceptions public static <U, V> List<V> map(List<U> l, Function<U,V> f) {
List<V> ret = new ArrayList<>();
Interfaces
graphiques for (U x : l) ret.add(f.apply(x));
return ret;
Concurrence
}
Exemple
Compléments
en POO Interfaces fonctionnelles
Aldric Degorre Écrire les types des FPC, c’est bien. Mais comment exécuter une fonction ?
Introduction

Style Comme on a déjà pu voir sur les exemples :


Objets et
classes • Il n’y a pas de syntaxe réservée pour exécuter une expression fonctionnelle.
Types et
polymorphisme • Il faut donc à chaque fois appeler explicitement la méthode de l’interface
Héritage
fonctionnelle concernée (et donc connaître son nom...).
Généricité

Exemple :
Collections
Généricité :
introduction
Effacement de type,
variance, tableaux Function<Integer, Integer> carre = n -> n * n;
Lambda-expressions
System.out.println(carre.apply(5)); // <--- ici c'est apply
Les “streams”
Wildcards

Gestion des
erreurs et mais...
exceptions

Interfaces
Predicate<Integer> estPair = n -> (n % 2) == 0;
graphiques System.out.println(estPair.test(5)); // <--- là c'est test
Concurrence
Compléments
en POO Syntaxe des lambda-expressions
Aldric Degorre lambda-abstraction : par l’exemple

Introduction Écrire une fonction anonyme par lambda-abstraction :


Style
<paramètres> -> <corps de la fonction>
Objets et
classes

Types et Exemples :
polymorphisme

Héritage

x -> x + 2
Généricité

(raccourci pour (int x)-> { return x + 2; })


Collections
Généricité :
introduction
Effacement de type, •
variance, tableaux (x, y) -> x + y
Lambda-expressions
Les “streams”
Wildcards
(raccourci pour (int x, int y)-> { return x + y; })
Gestion des
erreurs et

(a, b) -> {
exceptions
int q = 0;
Interfaces while (a >= b) { q++; a -= b; }
graphiques
return q;
Concurrence }
Compléments
en POO Syntaxe des lambda-expressions
Aldric Degorre lambda-abstraction : syntaxe formelle

Introduction

Style
<paramètres> -> <corps de la fonction>
Objets et
classes

Types et
polymorphisme
Syntaxe en détails :
Héritage • <paramètres> : liste de paramètres formels (de 0 à plusieurs), de la forme
Généricité
Collections (int x, int y, String s)
Mais juste (x,y,s) fonctionne aussi (type des paramètres inféré).
Généricité :
introduction
Effacement de type,

Et parenthèses facultatives quand il y a un seul paramètre.


variance, tableaux
Lambda-expressions
Les “streams”
Wildcards • <corps de la fonction>, au choix :
Gestion des
erreurs et • une simple expression, p. ex. (x==y)?s:""
exceptions • une liste d’instructions entre accolades, contenant une instruction return si type de
Interfaces
graphiques
retour non void
Concurrence

Détails
Compléments
en POO Syntaxe des lambda-expressions
Aldric Degorre Référence de méthode

Introduction

Style

Objets et
classes
Pour créer une lambda-expression contenant juste l’appel d’une méthode existante :
Types et
polymorphisme
• on peut utiliser la lambda-abstraction :
Héritage x -> Math.sqrt(x)
Généricité
Collections • mais il existe une notation encore plus compacte :
Généricité :
introduction
Effacement de type,
Math::sqrt
Ceci s’appelle une référence de méthode
variance, tableaux
Lambda-expressions
Les “streams”
Wildcards

Gestion des
Remarque :
erreurs et Math::sqrt est bien équivalent à x -> Math.sqrt(x), et non à
exceptions

Interfaces
(double x)-> Math.sqrt(x). Cela a une incidence pour l’inférence de type (cf. la suite).
graphiques

Concurrence
Compléments
en POO Syntaxe des lambda-expressions
Aldric Degorre Référence de méthode : les cas de figure

Introduction Supposons la classe suivante définie :


Style
class C {
Objets et int val;
classes
C(int val) { this.val = val; }
Types et static int f(int n) { return n; }
polymorphisme
int g(int n) { return val + n; }
Héritage }
Généricité
Collections
Généricité :
introduction
La notation “référence de méthode” se décline pour différents cas de figure :
Effacement de type,

• Méthode statique → C::f pour n -> C.f(n)


variance, tableaux
Lambda-expressions
Les “streams”
Wildcards • Méthode d’instance avec récepteur donné → avec x = new C(), on écrit x::g
Gestion des
erreurs et pour n -> x.g(n)
exceptions
• Méthode d’instance sans récepteur donné → C::g pour (x, n)-> x.g(n)
Interfaces
graphiques • Constructeur → C::new pour n -> new C(n)
Concurrence

Détails En cas de surcharge, Java déduit la méthode référencée du type attendu.


Compléments
en POO Utiliser les méthodes (FOS) de nos exemples...
Aldric Degorre ... en leur passant des lambda-expressions

Introduction

Style
// Dire 5 fois "Bonjour !" :
Objets et repeat5(() -> { System.out.println("Bonjour␣!"); });
classes

Types et
polymorphisme // Tirer pile ou face
Héritage ifElse( () -> Math.random() > 0.5,
() -> { System.out.println("pile"); },
Généricité
Collections () -> { System.out.println("face"); }
Généricité : );
introduction
Effacement de type,
variance, tableaux
Lambda-expressions
Les “streams”
// Essayer d'ouvrir un fichier jusqu'à 3 fois
Wildcards retry(() -> { ouvre("monFichier.txt"); }, 3);
Gestion des
erreurs et
exceptions // Calculer les racines carrées des nombres d'une liste
Interfaces List<Double> racines = map(maListe, Math::sqrt);
graphiques

Concurrence
Exemple
Compléments
en POO Ce qui se cache derrière les lambda-expressions
Aldric Degorre

Introduction

Style

Objets et
Nous avons montré :
classes

Types et
• d’une part, les types qui sont utilisés pour les FPC en Java (interfaces
polymorphisme
fonctionnelles)
Héritage

Généricité
• d’autre part, la syntaxe permettant d’écrire des FPC (lambda-expressions)
Collections

Deux questions se posent alors :


Généricité :
introduction
Effacement de type,
variance, tableaux
Lambda-expressions
Les “streams”
• À la compilation, étant donnée une lambda-expression, quel type le compilateur lui
Wildcards
donne-t-il ?
Gestion des
erreurs et
exceptions
• À l’exécution, comment est évaluée une lambda-expression ?
Interfaces
graphiques

Concurrence
Compléments
en POO Ce qui se cache derrière les lambda-expressions
Aldric Degorre Concernant le typage (1)

Introduction

Style • Hors contexte, une lambda-expression n’a pas de type (plusieurs types possibles).
Objets et
classes • En contexte, sous réserve de compatibilité, son type est le type attendu à son
Types et emplacement dans le programme (inférence de type).
polymorphisme

Héritage • Compatibilité si :
Généricité • le type attendu est défini par une interface fonctionnelle (= est un type SAM)
Collections
Généricité : • et la méthode abstraite de cette interface est redéfinissable par une méthode qui
introduction
Effacement de type,
variance, tableaux
aurait la même signature et le même type de retour que la lambda-expression. 273
Lambda-expressions
Les “streams”
Wildcards
Exemple, on peut écrire Function<Integer,Double> f = x -> Math.sqrt(x);
Gestion des
erreurs et car l’interface Function est comme suit :
exceptions
public interface Function<T,R> { R apply(T t); } // apply a une signature compatible
Interfaces
graphiques

Concurrence
273. Ou bien, dans le cas où les types des arguments ne sont pas précisé, s’il existe une façon de les ajouter
qui rend la signature compatible.
Compléments
en POO Ce qui se cache derrière les lambda-expressions
Aldric Degorre Concernant le typage (2) — petits pièges

Introduction

Style
Le fait de préciser le type des paramètres d’une lambda-expression restreint les
Objets et possibilités d’utilisation.
classes

Types et
Ces exemples compilent :
polymorphisme
Function<Integer,Double> f = x -> Math.sqrt(x);
Héritage
Function<Integer,Double> f = (Integer x) -> Math.sqrt(x);
Généricité
Collections

Mais ceux-ci ne compilent pas :


Généricité :
introduction
Effacement de type,
variance, tableaux
Lambda-expressions Function<Integer,Double> f = (Double x) -> Math.sqrt(x);
Les “streams”
IntFunction<Double> f = (double x) -> Math.sqrt(x); // pourtant la lambda-expression
Wildcards
accepte double (plus large que int).
Gestion des
erreurs et
exceptions

Interfaces
Remarquablement, ceci compile (malgré le fait que sqrt ait un paramètre double) :
graphiques
Function<Integer,Double> f = Math::sqrt;
Concurrence

Détails
Compléments
en POO Ce qui se cache derrière les lambda-expressions
Aldric Degorre Concernant le typage (3)

Introduction

Style Attention : n’importe quel type SAM peut être le type d’une lambda-expression. Pas
Objets et
classes
seulement ceux définis dans java.util.function.
Types et
polymorphisme
Partout où une expression de type SAM attendue, on peut utiliser une lambda-expression,
Héritage
même si ce type (ou la méthode qui l’attend) date d’avant Java 8.
Généricité
Collections
Ainsi, en Swing, à la place de la classique “invocation magique” :
Généricité :
introduction SwingUtilities.invokeLater(
Effacement de type,
variance, tableaux new Runnable() {
Lambda-expressions public void run() { MonIG.build(); }
Les “streams”
Wildcards
}
);
Gestion des
erreurs et
exceptions

Interfaces
on peut écrire : SwingUtilities.invokeLater(()-> MonIG.build());
graphiques

Concurrence
ou encore mieux : SwingUtilities.invokeLater(MonIG::build);
Compléments
en POO Ce qui se cache derrière les lambda-expressions
Aldric Degorre Concernant l’évaluation

Introduction
À l’évaluation, Java construit un objet singleton (instance d’une classe anonyme) qui
Style
implémente l’interface fonctionnelle en utilisant la fonction décrite dans la
Objets et
classes lambda-expression.
Types et
polymorphisme Toujours dans le même exemple, javac sait alors qu’il doit compiler l’instruction
Héritage
Function<Integer,Double> f = x -> Math.sqrt(x);
Généricité
Collections

de la même façon 274 que :


Généricité :
introduction
Effacement de type,
variance, tableaux
Lambda-expressions Function<Integer,Double> f = new Function<Integer, Double>() {
Les “streams”
Wildcards
@Override public Double apply(Integer x) {
return Math.sqrt(x);
Gestion des
erreurs et }
exceptions };
Interfaces
graphiques
274. En fait, pour les lambdas, la JVM construit la classe anonyme à l’exécution seulement. À cet effet, javac
Concurrence
a en fait compilé l’expression en écrivant l’instruction invokedynamic (introduite dans Java 8 dans ce but).
Autrement, les classes, même anonymes, sont créées à la compilation et existent déjà dans le code octet.
Compléments
en POO Avertissement
Aldric Degorre Attention aux variables locales !

Introduction

Style
• Valeur d’une lambda-expression = instance de classe locale (anonyme).
Objets et
classes
• Ainsi, une lambda-expression de Java ne peut utiliser que les variables locales
Types et effectivement final. 275
polymorphisme

Héritage
• Comparaison avec OCaml : en OCaml, cette “limitation” n’est pas perçue car, en
Généricité effet, les “variables” ne sont pas réaffectables 276 (tout est “final”).
Comme Java, OCaml se contente de recopier les valeurs des variables locales dans
Collections
Généricité :
introduction
Effacement de type,
variance, tableaux
la clôture de la fonction (≃ l’instance de la classe locale).
Lambda-expressions
Les “streams”
Wildcards
275. Rappel : dans une classe locale, on a accès aux variables locales seulement si elles sont effectivement
final (c.-à-d. jamais modifiées après leur initialisation). Cette restriction permet d’éviter les incohérences
Gestion des
erreurs et (modifications locales non partagées).
exceptions
276. On simule des données locales modifiables en manipulant des “références” mutables :
Interfaces let ref x = 42 in x := !x + 1; x;
graphiques
Dans l’exemple, x n’est pas réaffectable, mais la valeur stockée à l’adresse contenue dans x l’est.
Concurrence
L’équivalent en Java serait un objet-boîte contenant un unique attribut non final. D’ailleurs, rien n’empêche
d’utiliser cette technique en Java ; il faut juste l’écrire “à la main”.
Compléments
en POO Avertissement
Aldric Degorre Exemple :

Introduction Incorrect :
Style
int a = 1;
Objets et a++; // a réaffectée
classes
Function<Integer, Integer> f = x -> { return x + a; };
Types et
polymorphisme

Héritage Correct :
Généricité final int a = 1; // a final (non réaffectable)
Collections
Généricité :
Function<Integer, Integer> f = x -> { return x + a; };
introduction
Effacement de type,

Aussi correct :
variance, tableaux
Lambda-expressions
Les “streams”
Wildcards int a = 1; // a effectivement final (non réaffectée)
Gestion des Function<Integer, Integer> f = x -> { return x + a; };
erreurs et
exceptions

Interfaces Et correct aussi :


graphiques
class IntRef { int val; IntRef(int val) { this.val = val; } }
Concurrence
final IntRef a = new IntRef(12);
Exemple Function<Integer, Integer> f = x -> { return a.val += x; /* modification de a */ };
Compléments
en POO Fonctions récursives
Aldric Degorre

Introduction

Style • Supposons qu’on veuille définir une fonction récursive, comme en OCaml :
Objets et
classes l e t rec fact = f u n c t i o n
| 0 -> 1
Types et
polymorphisme | n -> n * fact (n - 1);;

Héritage

Généricité • Sachant qu’il n’existe pas l’équivalent de let rec en Java, peut-on définir ?
Collections
Généricité :
introduction IntUnaryOperator fact = n -> (n==0)?1:(n*fact.applyAsInt(n-1));
Effacement de type,
variance, tableaux
Lambda-expressions
Les “streams” • Problème : la variable fact n’est pas encore déclarée quand on compile la
Wildcards

Gestion des
lambda-expression =⇒ la compilation échoue.
erreurs et
exceptions
Il existe des dizaines de façons de contourner cette limite, mais la seule qui soit
Interfaces
graphiques élégante consiste à définir d’abord une méthode récursive.
Concurrence

Discussion
Compléments
en POO Fonctions récursives
Aldric Degorre

Introduction Pour définir cette FPC, il faudrait donc faire en 2 temps :


Style

Objets et
1 initialiser fact (à null par exemple)
classes
2 écrire la lambda-expression (utilisant fact) et l’affecter à fact.
Types et
polymorphisme

Héritage Il faudrait donc que la variable fact soit réaffectable... donc fact ne pourrait pas être
Généricité locale.
Collections
Généricité :
introduction → Il faudrait que fact soit un attribut, mais alors attention à l’encapsulation.
Effacement de type,

Une possibilité, respectant l’encapsulation, à l’aide d’une classe locale auxiliaire :


variance, tableaux
Lambda-expressions
Les “streams”
Wildcards
class FunRef { IntUnaryOperator val; }
Gestion des final FunRef factAux = new FunRef();
erreurs et factAux.val = n -> (n==0)?1:(n*factAux.val.applyAsInt(n-1));
exceptions
IntUnaryOperator fact = factAux.val;
Interfaces
graphiques

Concurrence Inconvénient : niveau d’indirection supplémentaire.


Discussion
Compléments
en POO Fonctions récursives
Aldric Degorre

Alternative : déclarer la factorielle comme méthode privée récursive, puis manipuler une
Introduction

Style
référence vers celle-ci.
Objets et class Autre2 {
classes ...
Types et private int fact(int n) { return (n==0)?1:(n*fact(n-1)); }
polymorphisme ...
Héritage

Généricité
IntUnaryOperator fact = Autre::fact;
Collections ...
Généricité : }
introduction
Effacement de type,
variance, tableaux

Et si on veut tout encapsuler correctement, on revient à une classe anonyme classique :


Lambda-expressions
Les “streams”
Wildcards

Gestion des IntUnaryOperator fact = new IntUnaryOperator() {


erreurs et @Override public int applyAsInt(int n) { return (n==0)?1:(n*applyAsInt(n-1)); }
exceptions }
Interfaces
graphiques

Concurrence Cette dernière technique n’a pas d’inconvénient 277 . C’est donc celle qu’il faut privilégier.
Discussion 277. si, un : on troque la syntaxe des lambda-expressions contre celle, plus verbeuse, des classes anonymes
Compléments
en POO Bilan des FPC dans Java 8
Aldric Degorre Le mauvais

Introduction
• Pas de notation (flèche) dédiée aux types fonctionnels, juste interfaces classiques.
Style
• Plusieurs interfaces possibles pour une même fonction.
Objets et
classes • Pas de syntaxe réservée, unique, pour exécuter une FPC.
Types et
polymorphisme À la place : appel de la méthode de l’interface, dont le nom peut varier.
Héritage • Clôture contenant variables effectivement finales seulement.
Généricité
Collections Implication : nécessité de “contourner” pour capturer un état mutable. 278
• Impossibilité de définir une fonction récursive 279 juste avec une
Généricité :
introduction
Effacement de type,
variance, tableaux
Lambda-expressions lambda-expression.
• Malgré les apports de Java 8 à 11, le JDK contient peu d’APIs dans le style
Les “streams”
Wildcards

Gestion des
erreurs et
fonctionnel (On peut néanmoins citer Stream et CompletableFuture).
exceptions

Interfaces
Java n’est toujours pas un langage de PF, mais juste un LOO avec support limité des FPC.
graphiques

Concurrence 278. Cela dit, on évite d’utiliser un état mutable en PF.


Résumé 279. De plus Java n’optimise pas la récursivité terminale. Ainsi, l’appel d’une méthode récursive sur des
Compléments
en POO Bilan des FPC dans Java 8
Aldric Degorre Le bon

Introduction • Java supporte les FPC et les FOS.


Style
Or les FPC sont amenées à jouer un rôle de plus en plus important, notamment pour
Objets et
classes la programmation concurrente 280 , qui devient de plus en plus incontournable 281 .
Types et
polymorphisme • Les API Stream et CompletableFuture sont d’excellents exemples d’API
Héritage concurrentes, introduites dans Java 8 et utilisant les FOS.
Généricité
Collections • D’anciennes API se retrouvent immédiatement utilisables avec les
lambda-expressions car utilisant déjà des interfaces fonctionnelles (e.g. JavaFX).
Généricité :
introduction
Effacement de type,

→ nouvelle concision, “gratuite”.


variance, tableaux
Lambda-expressions
Les “streams”
Wildcards Malgré ses défauts, le support des FPC et FOS dans Java est un apport indéniable.
Gestion des
erreurs et 280. Quel est le rapport entre FPC/FOS et programmation concurrente ? Plusieurs réponses :
exceptions • la programmation concurrente incite à utiliser des structures immuables pour garantir la correction du programme.
Interfaces Or le style fonctionnel est naturel pour travailler avec les structures immuables.
graphiques • en programmation concurrente, on demande souvent l’exécution asynchrone d’un morceau de code. Pour ce faire,
Concurrence ce dernier doit être passé en argument d’une fonction (FOS) sous la forme d’une FPC.
Résumé 281. En particulier à cause de la multiplication du nombre de cœurs dans les microprocesseurs.
Compléments
en POO Opérations d’agrégation
Aldric Degorre Définition et quelques exemples

Introduction

Style
Opération d’agrégation : traitement d’une séquence de données de même type qui
Objets et
classes produit un résultat synthétique 282 dépendant de toutes ces données.
Types et
polymorphisme Exemples :
Héritage

Généricité • calcul de la taille d’une collection


Collections
Généricité :
introduction
• concaténation des chaînes d’une liste de chaînes
Effacement de type,
variance, tableaux
Lambda-expressions
• transformation d’une liste de chaînes en la liste de ses longueurs
Les “streams”
Wildcards (ex : “bonjour”, “le”, monde → 7, 2, 5)
Gestion des
erreurs et
• recherche d’un élément satisfaisant un certain critère
exceptions

Interfaces Tous ces calculs pourraient s’écrire à l’aide de boucles for très similaires...
graphiques

Concurrence
Analyse 282. synthèse = résumé
Compléments
en POO Opérations d’agrégation
Aldric Degorre Quelques exemples (1)

Introduction

Style

Objets et
Calcul de la taille d’une collection :
classes
public static int size(Collection<?> dataSource) {
Types et
polymorphisme
int acc = 0;
for (Object e: dataSource) acc++;
Héritage
return acc;
Généricité }
Collections
Généricité :
introduction
Effacement de type,
variance, tableaux
Concaténation des chaînes d’une liste de chaînes :
Lambda-expressions
Les “streams” public static String concat(List<String> dataSource) {
Wildcards
String acc = "";
Gestion des for (String e: dataSource) acc += e.toString();
erreurs et
exceptions
return acc;
}
Interfaces
graphiques

Concurrence
Analyse
Compléments
en POO Opérations d’agrégation
Aldric Degorre Quelques exemples (2)

Introduction

Style Transformation d’une liste de chaînes en la liste de ses longueurs :


Objets et public static List<Integer> lengths(List<String> dataSource) {
classes
List<Integer> acc = new LinkedList<>();
Types et for (String e: dataSource) acc.add(e.length());
polymorphisme
return acc;
Héritage }
Généricité
Collections
Généricité :
introduction Recherche d’un élément satisfaisant un certain critère 283 :
Effacement de type,
variance, tableaux
Lambda-expressions public static <E> E find(List<E> dataSource, Predicate<E> criterion) {
Les “streams” E acc = null;
Wildcards
for (E e: dataSource) acc = (criterion.test(e)?e:null);
Gestion des return acc;
erreurs et
exceptions }

Interfaces
graphiques
Voyez-vous le motif commun ?
Concurrence
Analyse 283. Remarque : on peut optimiser cette boucle, mais cette présentation illustre mieux le propos.
Compléments
en POO Opérations d’agrégation
Aldric Degorre Généralisation

Introduction

Style On garde ce qui est commun dans une méthode prenant en argument ce qui est différent :
Objets et
classes public static <E, R> R fold(Iterable<E> dataSource, R zero, ??? op) {
Types et R acc = zero;
polymorphisme for (E e : dataSource) acc = op(acc, e); // comment on écrit ça déjà ?
Héritage return acc;
}
Généricité
Collections
Généricité :
introduction
Effacement de type,
variance, tableaux
Lambda-expressions
Les “streams”
Wildcards

Gestion des
erreurs et
exceptions

Interfaces
graphiques

Concurrence
Analyse
Compléments
en POO Opérations d’agrégation
Aldric Degorre Généralisation

Introduction

Style On garde ce qui est commun dans une méthode prenant en argument ce qui est différent :
Objets et
classes public static <E, R> R fold(Iterable<E> dataSource, R zero, ??? op) {
Types et R acc = zero;
polymorphisme for (E e : dataSource) acc = op(acc, e); // comment on écrit ça déjà ?
Héritage return acc;
}
Généricité
Collections
Généricité :
introduction
Effacement de type,
... et on se rappelle le cours sur les fonctions de première classe (FPC) et les fonctions
variance, tableaux
Lambda-expressions d’ordre supérieur (FOS) :
Les “streams”
Wildcards
public static <E, R> R fold(Iterable<E> dataSource, R zero, BiFunction<R, E, R> op) {
Gestion des R acc = zero;
erreurs et for (E e : dataSource) acc = op.apply(acc, e);
exceptions
return acc;
Interfaces }
graphiques

Concurrence
Analyse
Compléments
en POO Opérations d’agrégation
Aldric Degorre Généralisation

Introduction
On peut alors écrire :
Style

Objets et public static int size(Collection<?> dataSource) {


classes return fold(dataSource, 0, (acc, e) -> acc + 1);
Types et }
polymorphisme

Héritage
public static String concat(List<String> dataSource) {
return fold(dataSource, "", (acc, e) -> acc + e.toString());
Généricité
}
Collections
Généricité :
introduction
public static List<Integer> lengths(List<String> dataSource) {
Effacement de type,
variance, tableaux return fold(dataSource, new LinkedList<>(),
Lambda-expressions
Les “streams”
(acc, e) -> { acc.add(e.length()); return acc; });
Wildcards }
Gestion des
// "Bof" : on modifie l'argument de op dans op (incorrect si fold est concurrent).
erreurs et // On doit pouvoir faire mieux (à méditer en TP... ) !
exceptions

Interfaces public static <E> E find(List<E> dataSource, Predicate<E> criterion) {


graphiques return fold(dataSource, null, (acc, e) -> (criterion.test(e) ? e : null));
Concurrence }
Analyse
Compléments
en POO Opérations d’agrégation
Aldric Degorre Généralisation

Introduction

Style

Objets et
classes

Types et
polymorphisme

Héritage
Pour écrire des traitements similaires à ces exemples, on aimerait une API fournissant
Généricité des FOS analogues à fold pour les principaux schémas d’itération 284 .
Collections
Généricité :
introduction
C’est justement ce que fait l’API stream. 285
Effacement de type,
variance, tableaux
Lambda-expressions
Les “streams”
Wildcards

Gestion des
erreurs et
exceptions

Interfaces
graphiques

Concurrence
284. Similaires aux fonctions de manipulation de liste en OCaml.
Analyse 285. Mais pas seulement...
Compléments
en POO L’API Streams
Aldric Degorre Ce dont il s’agit.

Introduction

Style
Streams : API introduite dans Java 8 pour effectuer des opérations d’agrégation.
Objets et
classes • API dans le style fonctionnel, avec fonctions d’ordre supérieur ;
Types et
polymorphisme • distincte de l’API des collections (nouvelle interface Stream, au lieu de méthodes
Héritage ajoutées à Collection 286 ) ;
Généricité
Collections
• optimisée pour les grands volumes de données : évaluation paresseuse (calculs
Généricité :
introduction effectués seulement au dernier moment, seulement lorsqu’ils sont nécessaires) ;
Effacement de type,

• qui sait utiliser les CPUs multi-cœur pour accélérer ses calculs (implémentation
variance, tableaux
Lambda-expressions

parallèle multi-threadée).
Les “streams”
Wildcards

Gestion des
erreurs et
exceptions Avertissement : ce chapitre traite du package java.util.stream introduit dans Java 8. Ces
Interfaces
graphiques
streams n’ont aucun rapport avec les classes InputStream et OutputStream de java.io.
Concurrence
286. Heureusement on obtient facilement une instance de de Stream depuis une instance de Collection,
grâce à la méthode stream de Collection.
Compléments
en POO Opérations d’agrégation
Aldric Degorre Avec les streams

Introduction

Style
Avec l’API stream, une telle opération se décompose sous forme d’un pipeline d’étapes
Objets et
classes successives, selon le schéma suivant :
Types et
polymorphisme
1 sélection d’une source d’éléments 287 . Depuis cette source on obtient un stream.
Héritage

Généricité 2 un certain nombre (0 ou plus) d’opérations intermédiaires. Ces opérations


transforment un stream en un autre stream.
Collections
Généricité :
introduction

une opération terminale qui transforme le stream en un résultat final (qui n’est plus
Effacement de type,
variance, tableaux 3
Lambda-expressions
Les “streams” un stream).
Wildcards

Gestion des
erreurs et Les calculs sont effectués à l’appel de l’opération terminale seulement. Et seuls les
exceptions
calculs nécessaires le sont.
Interfaces
graphiques

Concurrence
287. Souvent une collection, mais peut aussi être un tableau, une fonction productrice d’éléments, un canal
d’entrées/sorties
Compléments
en POO Les objets stream
Aldric Degorre Exemples

Introduction

Style
Quelques streams :
Objets et
classes

Types et
• Pour toute collection coll, le stream associé est coll.stream().
polymorphisme

Héritage
• Stream.of(4,39,2,12,32) représente la séquence 4, 39, 2, 12, 32.
Généricité • Stream.of(4,39,2,12,32).map(x -> 2 * x) représente la séquence
Collections
Généricité :
introduction
8, 78, 4, 24, 64.
Effacement de type,

Inversement, on peut ensuite obtenir une collection depuis un stream :


variance, tableaux
Lambda-expressions
Les “streams”
Wildcards
Stream.of(4,39,2,12,32).map(x -> 2 * x).collect(Collectors.toList)
Gestion des
erreurs et
exceptions
→ on obtient un List<Integer> (collect, opération terminale, force le calcul de la
Interfaces
graphiques séquence).
Concurrence
Exemple
Compléments
en POO Les objets stream
Aldric Degorre Caractéristiques principales

Introduction

Style Qu’est-ce qu’un stream ? → 2 points de vue :


Objets et
classes
1 la représentation implicite d’une séquence d’éléments finie ou infinie
Types et
polymorphisme
2 la description d’une suite d’opérations permettant d’obtenir cette séquence.
Héritage

Généricité
Collections
Remarques importantes :
Généricité :

• Un objet stream n’est pas une collection : il ne contient qu’une référence vers une
introduction
Effacement de type,
variance, tableaux
Lambda-expressions
Les “streams”
source d’éléments (parfois une collection, souvent un autre stream) et la
Wildcards
description d’une opération à effectuer.
Gestion des
erreurs et • Un objet stream n’est pas le résultat d’un calcul, mais la description d’un calcul à
exceptions

Interfaces
effectuer 288 .
graphiques

Concurrence
288. Pour les fans de programmation fonctionnelle : le type Stream<T> muni des opérations of et
flatMap est une monade.
Compléments
en POO Les objets stream
Aldric Degorre Stream versus Iterator

Introduction

Style
Les streams et les iterators ont beaucoup en commun :
Objets et
classes
• intermédiaires techniques pour parcourir les collections
Types et
polymorphisme
• contiennent juste l’information pour faire cela ; pas les éléments eux-mêmes ;
Héritage • usage unique (après le premier parcours de la source, l’objet ne peut plus servir)
Généricité

Et une grosse différence :


Collections
Généricité :
introduction
Effacement de type,
variance, tableaux
Lambda-expressions
• streams : opérations agissant sur l’ensemble des éléments (itération implicite). Ce
Les “streams”
Wildcards
sont les méthodes fournies dans le JDK qui gèrent l’itération (et proposent
Gestion des notamment une implémentation en parallèle sur plusieurs threads 289 ).
erreurs et
exceptions • iterators : 1 opération (next) = lire l’élément suivant (→ il faut itérer explicitement)
Interfaces
graphiques
289. En fait, les streams sont basés sur des objets de type Spliterator<T> : sorte d’itérateur évolué
Concurrence
capable, en plus d’itérer séquentiellement, de couper une collection en morceaux (“split”) pour partager le
Discussion travail, notamment entre plusieurs threads.
Compléments
en POO Quelques exemples de traitements réalisables en utilisant les streams
Aldric Degorre

Introduction

Style 15 nombres entiers aléatoires positifs inférieurs à 100 en ordre croissant :


Objets et
classes Stream.generate(Math::random)
.limit(15)
Types et
polymorphisme .map(x -> (int)(100 * x))
.sorted()
Héritage
.collect(Collectors.toList());
Généricité
Collections

Nombre d’usagers d’une bibliothèque ayant emprunté un livre d’Alexandre Dumas :


Généricité :
introduction
Effacement de type,
variance, tableaux
Lambda-expressions bibli.getLivres().stream()
Les “streams”
Wildcards
.filter(livre -> livre.getAuteur().equals("Dumas,␣Alexandre"))
.flatMap(livre -> livre.getEmprunteurs().stream())
Gestion des
erreurs et
.distinct()
exceptions .count();
Interfaces
graphiques

Concurrence
Exemple
Compléments
en POO Streams et parallélisme
Aldric Degorre Problème à résoudre

Introduction

Style

Objets et
classes • la plupart des collections ne sont pas thread safe (peuvent avoir un comportement
Types et
polymorphisme
incorrect quand utilisées dans plusieurs threads en même temps)
Héritage • il y a des façons d’y ajouter de la synchronisation (voir collections synchronisées),
Généricité
Collections
mais toujours le risque de dead-locks, live-locks, famines, etc.
Généricité :
introduction
Effacement de type,
variance, tableaux
Pourtant, accélérer le traitement les grandes collections, il est utile de profiter du
Lambda-expressions
Les “streams”
parallélisme.
Wildcards

Gestion des Les streams, dont les opérations ne modifient pas le contenu de leur source 290 , sont une
erreurs et
exceptions réponse naturelle à ce problème.
Interfaces
graphiques

Concurrence

290. → pas d’accès en compétition


Compléments
en POO Streams et parallélisme
Aldric Degorre Streams séquentiels et streams parallèles

Introduction

Style

Objets et
classes
→ Java permet de lancer les opérations d’agrégation en parallèle, sans presque rien
Types et
polymorphisme changer à l’invocation du même traitement en séquentiel :
Héritage

Généricité • Il suffit de créer le stream avec maCollection.parallelStream() à la place


de maCollection.stream().
Collections
Généricité :
introduction
Effacement de type,
variance, tableaux • Alternative : à partir d’un stream séquentiel, on peut obtenir un stream parallèle
Lambda-expressions
Les “streams” avec la méthode parallel(), et vice-versa avec la méthode sequential()
Wildcards

Gestion des
erreurs et → dans le même pipeline, il peut y avoir à la fois des étapes séquentielles et parallèles.
exceptions

Interfaces
graphiques

Concurrence
Compléments
en POO Streams et parallélisme
Aldric Degorre Quelques explications

Introduction

Style • Les calculs d’un pipeline parallèle sont répartis sur plusieurs threads. 291 292
Objets et
classes • Leur ordre d’exécution peut être sans rapport avec celui des éléments de la source.
Types et
polymorphisme
• L’opération terminale retourne après que tous les calculs parallèles sont terminés.
Héritage • Certaines opérations garantissent que les éléments sont traités dans l’ordre, s’ils en
Généricité
Collections
avaient un (p. ex. : forEachOrdered), d’autres non (forEach).
Généricité :
introduction
Effacement de type,
• Imposer l’ordre (→ synchronisation) peut faire perdre le bénéfice du parallélisme.
variance, tableaux
Lambda-expressions
Les “streams”
• Certaines opérations sont optimisées pour le traitement parallèle (p. ex.
Wildcards
.collect(Collectors.toConcurrentMap(...) plus efficace que
Gestion des
erreurs et .collect(Collectors.toMap(...)).
exceptions

Interfaces 291. Le travail est (de façon invisible pour l’utilisateur) séparé en petites tâches soumises au thread pool par
graphiques
défaut : ForkJoinPool.commonPool(). On peut aussi explicitement demander d’en utiliser un autre.
Concurrence
292. Cette répartition est faite dans l’opération terminale. Rappel : les méthodes des opérations
intermédiaires ne servent qu’à déclarer une étape de traitement supplémentaire, à effectuer plus tard.
Compléments
en POO Streams et parallélisme
Aldric Degorre Piège à éviter : les effets de bord

Introduction

Style
En général, évitez les effets de bord 293 dans le pipeline, préférez les fonctions pures 294 .
Objets et
classes • Pour les entrées/sorties, au mieux, pas de contrôle sur leur ordre.
Types et
polymorphisme
• Pour les modifications d’objets partagés :
Héritage • sans synchronisation, risque d’accès en compétition. Or, dans ce cas, le modèle de
Généricité concurrence de Java ne garantit rien (→ résultats incorrects).
Collections
Généricité :
• avec synchronisation : comme on ne contrôle pas l’ordre d’exécution des tâches du
pipeline, risque de dead lock.
introduction
Effacement de type,

Sinon, de toute façon, la synchronisation ralentit l’exécution.


variance, tableaux
Lambda-expressions
Les “streams”
Wildcards
Heureusement, habituellement 295 , il est inutile de modifier des objets extérieurs
Gestion des
erreurs et dans les opérations d’un stream.
exceptions

Interfaces 293. Effet de bord : tout effet externe d’une fonction, c’est à dire toute sortie physique ou modification de
graphiques
mémoire en dehors de son espace propre (= variables locales + champs des objets non partagés).
Concurrence
294. Fonction pure : fonction (méthode ou FPC) sans effet de bord.
Discussion 295. À part à des fins de déboguage ou de monitoring.
Compléments
en POO Appendice : les méthodes de l’API stream
Aldric Degorre

Introduction

Style

Objets et
classes

Types et
polymorphisme Les transparents qui suivent :
Héritage
• sont un résumé des méthodes proposées dans l’API stream.
Généricité
Collections
Généricité :
• ne sont pas détaillés en cours magistral
introduction
Effacement de type,
variance, tableaux • doivent servir de référence pour les TPs et pour la relecture approfondie du cours.
Lambda-expressions
Les “streams”
Wildcards

Gestion des
erreurs et
exceptions

Interfaces
graphiques

Concurrence

Détails
Compléments
en POO L’interface Stream
Aldric Degorre L’interface principale du package

Introduction

Style
public interface Stream<T> { // pour des éléments de type T
...
Objets et
}
classes

Types et
polymorphisme
(Il existe aussi DoubleStream, IntStream et LongStream.)
Héritage

Généricité Cette interface contient un grand nombre de méthodes. 3 catégories :


Collections

• des méthodes statiques 296 servant à créer des streams depuis des sources
Généricité :
introduction
Effacement de type,
variance, tableaux
Lambda-expressions
diverses.
Les “streams”
Wildcards • des méthodes d’instance transformant des streams en streams (pour les opérations
Gestion des
erreurs et
intermédiaires)
exceptions
• des méthodes d’instance transformant des streams en autre chose (pour les
Interfaces
graphiques opérations terminales).
Concurrence

Détails 296. Rappel : oui, c’est possible depuis Java 8.


Compléments
en POO Fabriquer un stream depuis une source
Aldric Degorre

• Depuis une collection : méthode Stream<T> stream()de Collection<T>.


Introduction

Style • À l’aide d’une des méthodes statiques de Stream :


Objets et
classes
• <T> Stream<T> empty() : retourne un stream vide
Types et
• <T> Stream<T> generate(Supplier<T> s) : retourne la séquence des
polymorphisme éléments générés par s.get() (Rappel : Supplier<T> = fonction de {()} →T.).
Héritage • <T> Stream<T> iterate(T seed, UnaryOperator<T> f : retourne la
Généricité séquence des éléments seed, f.apply(seed), f.apply(f.apply(seed)) ...
Collections
Généricité : • <T> Stream<T> of(T... values) : retourne le stream constitué de la liste des
introduction
Effacement de type, éléments passés en argument (méthode d’arité variable).
variance, tableaux

• En utilisant un builder 297 (Stream.Builder) :


Lambda-expressions
Les “streams”
Wildcards

Gestion des
• Un Stream.Builder est un objet mutable servant à construire un stream.
erreurs et • On instancie un builder vide avec l’appel statique b = Stream.builder()
exceptions
• On ajoute des éléments avec les appels b.add(T e) ou b.accept(T e).
Interfaces
graphiques • On finalise en créant le stream contenant ces éléments : appel s = b.build()
Concurrence
297. On parle du patron de conception builder (ou “monteur”), ici appliqué aux streams. Ainsi, par exemple, il
Détails existe une classe StringBuilder jouant le même rôle pour les String. Voir le TP sur le patron builder.
Compléments
en POO Transformer un stream : les opérations intermédiaires (1)
Aldric Degorre
Pour this instance de Stream<T> :
Introduction

Style Stream<T> distinct()
Objets et
classes retourne un stream qui parcourt les éléments de this sans les doublons.
Types et •
polymorphisme Stream<T> filter(Predicate<? super T> p)
Héritage

Généricité retourne le stream parcourant les éléments x de this qui satisfont p.test(x)
Collections
Généricité :

introduction
<R> Stream<R> flatMap(Function<? super T,? extends Stream<? extends R>> mapper)
Effacement de type,
variance, tableaux
Lambda-expressions retourne la concaténation des streams mapper.apply(x) pour tout x dans this.
Les “streams”
Wildcards

Stream<T> limit(long n)
Gestion des
erreurs et
exceptions tronque le stream après n éléments.
Interfaces •
graphiques <U> Stream<U> map(Function<T, U> f)
Concurrence

Détails retourne le stream des éléments f.apply(x) pour tout x élément de this.
Compléments
en POO Transformer un stream : les opérations intermédiaires (2)
Aldric Degorre

Introduction

Stream<T> peek(Consumer<? super T> c)
Style

Objets et
classes retourne un stream avec les mêmes éléments que this. À l’étape terminale, pour
Types et chaque élément x parcouru, c.consume(x) sera exécuté 298 .
polymorphisme

Héritage Stream<T> skip(long n)
Généricité
Collections
Généricité : retourne le suffixe de la séquence en sautant les n premiers éléments.

introduction
Effacement de type,
variance, tableaux Stream<T> sorted()
Lambda-expressions
Les “streams”

retourne la séquence, triée dans l’ordre naturel.


Wildcards

Gestion des
erreurs et •
exceptions Stream<T> sorted(Comparator<? super T> comparator)
Interfaces
graphiques
idem mais en suivant l’ordre fourni.
Concurrence

Détails 298. Remarque : c ne sert que pour ses effets de bord. peek peut notamment être utile pour le déboguage.
Compléments
en POO Calculer et extraire un résultat : les opérations terminales
Aldric Degorre Opérations spécialisées (1)

Introduction

Style

Objets et
classes • boolean allMatch(Predicate<? super T> p) retourne vrai si et
Types et
polymorphisme seulement si p est vrai pour tous les éléments du stream.
Héritage
• boolean anyMatch(Predicate<? super T> p) retourne vrai si et
Généricité
Collections seulement si p est vrai pour au moins un élément du stream.
Généricité :
introduction
Effacement de type,
• long count() retourne le nombre d’éléments dans le stream.
variance, tableaux
Lambda-expressions
Les “streams”
• Optional<T> findAny() : retourne un élément (quelconque) du stream ou rien
Wildcards
si stream vide (voir interface Optional<T>).
Gestion des
erreurs et
exceptions
• Optional<T> findFirst() : pareil, mais premier élément.
Interfaces
graphiques

Concurrence

Détails
Compléments
en POO Calculer et extraire un résultat : les opérations terminales
Aldric Degorre Opérations spécialisées (2)

Introduction

Style

Objets et
• void forEach(Consumer<? super T> action) : applique action à
classes
chaque élément.
Types et
polymorphisme • void forEachOrdered(Consumer<? super T> action) : pareil en
Héritage
garantissant de traiter les éléments dans l’ordre de la source si elle en avait un.
Généricité
Collections
Généricité :
• Optional<T> max(Comparator<? super T> comp) : retourne le maximum.
introduction
Effacement de type,
variance, tableaux
• Optional<T> min(Comparator<? super T> comp) : retourne le minimum.
Lambda-expressions
Les “streams”
Wildcards
• Object[] toArray() : retourne un tableau contenant les éléments du stream.
Gestion des
erreurs et
• <A> A[] toArray(IntFunction<A[]> generator) : retourne un tableau
exceptions
contenant les éléments du stream. La fonction generator sert à instancier un
Interfaces
graphiques tableau de la taille donnée par son paramètre.
Concurrence

Détails
Compléments
en POO Calculer et extraire un résultat : les opérations terminales
Aldric Degorre Opérations “couteau suisse” :

Introduction

Style • Optional<T> reduce(BinaryOperator<T> op : effectue la réduction du


Objets et
classes stream par l’opération d’accumulation associative op.
Types et
polymorphisme
• T reduce(T zero, BinaryOperator<T> op : idem, avec le zéro fourni.
Héritage • <U> U reduce(U z, BiFunction<U, ? super T, U> acc,
Généricité
Collections
BinaryOperator<U> comb) : idem avec accumulation vers autre
Généricité :
introduction type. 299
Effacement de type,
variance, tableaux
Lambda-expressions • <R> R collect(Supplier<R> z, BiConsumer<R, ? super T> acc,
BiConsumer<R,R> comb) : comme reduce avec accumulation dans objet
Les “streams”
Wildcards

Gestion des mutable.


erreurs et
exceptions
• <R,A> R collect(Collector<? super T,A,R> collector) : on parle
Interfaces
graphiques juste après.
Concurrence

Détails 299. Opération appelée fold dans d’autres langages. Les définitions varient...
Compléments
en POO L’interface Collector
Aldric Degorre interface Collector<T,A,R>

Introduction

Style Un Collector est un objet servant à “réduire” un stream en un résultat concret (en
Objets et effectuant le calcul). Pour ce faire,
classes

Types et
polymorphisme
• il initialise un accumulateur du type désiré (p. ex : liste vide),
Héritage • puis transforme et aggrège les éléments issus du calcul du stream dans
Généricité
Collections
l’accumulateur (ex : ajout à la liste)
Généricité :
introduction
Effacement de type,
• enfin il “finalise” l’accumulateur avant retour (p. ex : suppression des doublons).
variance, tableaux

Trois techniques pour fabriquer un tel objet :


Lambda-expressions
Les “streams”
Wildcards

Gestion des
erreurs et
• (cas courants) utiliser une des fabriques statiques de la classe Collectors
exceptions
• utiliser la fabrique statique Collector.of() ( “constructeur” généraliste)
Interfaces
graphiques
• programmer à la main une classe qui implémente l’interface Collector
Concurrence

Détails
Compléments
en POO La classe Collectors
Aldric Degorre

Introduction

Style Cette classe, non instanciable, est une bibliothèque de fabriques statiques pour obtenir
Objets et
classes
simplement les collectors les plus courants. Quelques exemples :
Types et
Collectors.toList(), Collectors.toSet(), Collectors.counting(),
polymorphisme
Collectors.groupingBy(...), Collectors.reducing(...),
Héritage
Collectors.toConcurrentMap(...)...
Généricité
Collections
Généricité :
→ on retrouve des opérations équivalentes 300 à la plupart des réductions de l’interface
Stream.
introduction
Effacement de type,
variance, tableaux

Ainsi, autre façon d’avoir la taille d’un stream :


Lambda-expressions
Les “streams”

monStream.collect(Collectors.counting()) 301 .
Wildcards

Gestion des
erreurs et
exceptions

Interfaces
graphiques
300. mais ici : implémentation “mutable”, utilisant un attribut accumulateur, alors que dans Stream, les
Concurrence
réductions utilisent de fonctions “pures”
Détails 301. ... mais le plus simple reste monStream.count() !
Compléments
en POO Construire un collector via l’interface Collector
Aldric Degorre

Introduction Au cas où la bibliothèque Collectors ne contient pas ce qu’on cherche, on peut créer
Style un Collector autrement :
Objets et
classes • créer et instancier une classe implémentant Collector.
Types et
polymorphisme Méthodes à implémenter : accumulator(), characteristics(),
Héritage combiner(), finished() et supplier().
Généricité
Collections
• sinon, créer directement l’objet grâce à la méthode statique Collector.of() :
Généricité :
introduction
Effacement de type,
c2 = Collector<Integer, List<Integer>, Integer> c2 = Collector.of(
variance, tableaux ArrayList<Integer>::new, List::add,
Lambda-expressions
Les “streams”
(l1, l2) -> {l1.addAll(l2); return l1;}, List::size
Wildcards );
Gestion des
erreurs et
exceptions (façon... un peu alambiquée de calculer la taille d’un stream... )
Interfaces
graphiques Utiliser la méthode of() est plus “légèr” syntaxiquement, mais ne permet pas d’ajouter
Concurrence des champs ou des méthodes à l’objet fabriqué.
Détails
Compléments
en POO Les génériques invariants c’est bien mais...
Aldric Degorre

Introduction Le besoin : l’invariance des génériques donne des garanties fortes : très bien, mais...
Style très rigide à l’usage ! On aimerait être autorisé à écrire Gen<A> g = new Gen<B>();
Objets et quand B <: A. (ou le contraire).
classes

Types et
polymorphisme • Cela favoriserait le polymorphisme (par sous-typage).
Héritage
• On le fait bien avec les tableaux (Object[] t = new String[10];).
Généricité
Collections
Généricité :
• C’est souvent conforme à l’intuition (cf. tableaux).
introduction
Effacement de type,

Mais on sait que ça risque d’être difficile :


variance, tableaux
Lambda-expressions
Les “streams”
Wildcards

Gestion des
• On a vu un contre-exemple pathologique (quand on force à compiler →
erreurs et
exceptions
ClassCastException possible).
Interfaces • On a vu les problèmes que posent les tableaux covariants
graphiques

Concurrence
(ArrayStoreException possible).
Analyse
Compléments
en POO Considérations autour de la variance (1)
Aldric Degorre

Introduction Pour les quelques pages qui suivent, oublions que Java impose l’invariance.
Style
Question : parmi les variables x, y, z et t, ci-dessous, lesquelles devrait-on,
Objets et
classes idéalement 302 , pouvoir affecter à quelles autres ?
Types et
polymorphisme class A {}
class B extends A {}
Héritage
// Interface pour fonctions F -> U (extraite de java.util.function) :
Généricité interface Function<T,U> { U apply(T t); }
Collections
Généricité :
class Test {
introduction Function<A,A> x;
Effacement de type,
variance, tableaux Function<A,B> y;
Lambda-expressions
Function<B,A> z;
Les “streams”
Wildcards Function<B,B> t;
}
Gestion des
erreurs et
exceptions

Interfaces (Traduction : quand est-ce qu’une instance Function<X,Y> fournit toutes les
graphiques
fonctionalités d’une instance de Function<Z,T> ?)
Concurrence
Analyse 302. par exemple dans un langage où les génériques pourraient ne pas être invariants
Compléments
en POO Considérations autour de la variance (2)
Aldric Degorre

Introduction

Style

Objets et
classes
Réponse : affecter u à v a un sens si la méthode apply de u peut remplacer celle de v
Types et
polymorphisme (en toute situation). C.-à-d. :
Héritage

Généricité
• si elle accepte des arguments pris dans un type au moins aussi large
Collections
Généricité :
introduction
• et si les valeurs retournées appartiennent à un type au moins aussi restreint.
Effacement de type,

→ en appliquant ce principe, on voudrait donc que le compilateur accepte :


variance, tableaux
Lambda-expressions
Les “streams”
Wildcards z = t; z = x; t = y; x = y; z = y;
Gestion des
erreurs et
exceptions

Interfaces
graphiques

Concurrence
Analyse
Compléments
en POO Considérations autour de la variance (3)
Aldric Degorre

Introduction
Représentation : type générique → pièce de puzzle. Paramètre utilisé en entrée → encoche.
Style
Paramètre utilisé en sortie → excroissance. Inclusion des formes en cas de sous-typage.
Objets et
classes
type de expr1 type de expr2
Types et
polymorphisme

Héritage

Généricité varx = expr1; ? varx = expr2; ?


Collections
Généricité :
introduction
type de varx (type attendu, en négatif)
Effacement de type,
variance, tableaux
Lambda-expressions
Les “streams”
Wildcards

Gestion des
erreurs et
exceptions

Interfaces → seul varx = expr2; doit fonctionner 303 (pas de chevauchement) :


graphiques

Concurrence
Analyse 303. Attention, on ne parle pas de Java, mais seulement d’un système de type “idéal”.
Compléments
en POO Considérations autour de la variance (4)
Aldric Degorre

Introduction
Ainsi,
Style

Objets et
• intuitif et logique de souhaiter
classes
Function<Object, Integer> <: Function<Double, Number>.
Types et
polymorphisme (Un paramètre utilisé uniquement pour l’argument de apply alors que l’autre est
Héritage uniquement son type de retour. Donc tailles de l’encoche et de l’excroissance de
Généricité Fuction<T, U> indépendantes. → Emboîtement de Fuction<T, U> dans
Collections
Généricité :
introduction
Function<V,W> possible si T :> V et U <: W.)
Effacement de type,
variance, tableaux
Lambda-expressions
• ... mais pas que List<Integer> <: List<Number>.
( le même paramètre apparait à la fois en sortie (get) et en entrée (set/add) 304 . Donc
Les “streams”
Wildcards

Gestion des tailles de l’encoche et de l’excroissance de List<T> liées car représentant le même T
erreurs et
exceptions → impossible d’encastrer la pièce de List<X> dans le trou List<Y> si X ̸= Y.)
Interfaces
graphiques
304. Même topo pour Integer[] <: Number[] avec les opérations x = t[i] et t[i] = x (... mais ça
Concurrence
c’est autorisé : en contrepartie, il est nécessaire de faire des vérifications à l’exécution, avec risque de
Analyse ArrayStoreException).
Compléments
en POO Encore un peu de vocabulaire autour de la variance (1)
Aldric Degorre

Introduction

Style

Objets et
classes
Dans Function<T, U>, T et U ont des influences contraires l’une de l’autre à cause de
Types et
polymorphisme leur usage dans la méthode de Function.
Héritage
→ 2 catégories d’usage :
Généricité
Collections
Généricité :
introduction
• en position covariante : utilisé comme type de retour de méthode (ou type d’attribut)
Effacement de type,
variance, tableaux
Lambda-expressions
• en position contravariante : utilisé comme type d’un argument dans la signature
Les “streams”
Wildcards
d’une méthode (ou comme type d’un attribut non final)
Gestion des
erreurs et
exceptions

Interfaces
graphiques

Concurrence
Compléments
en POO Encore un peu de vocabulaire autour de la variance (2)
Aldric Degorre

Introduction

Style
→ 3 catégories de paramètres de type :
Objets et
classes
• paramètre covariant (comme U) : utilisé seulement en position covariante
Types et
polymorphisme → plus le paramètre effectif est petit, plus le type paramétré devrait être petit ;
Héritage
• paramètre contravariant (comme T) : utilisé seulement en position contravariante
Généricité
Collections
Généricité :
→ plus le paramètre effectif est petit, plus le type paramétré devrait être grand ;
introduction
Effacement de type,
variance, tableaux
• paramètre invariant : utilisé à la fois en position covariante et contravariante.
Lambda-expressions
Les “streams”
Wildcards Attention, ces concepts ne sont que théoriques.
Gestion des
erreurs et Il se trouve que ceux-ci n’ont pas de sens pour le compilateur de Java : rappelez-vous
exceptions
qu’on avait dit que, pour l’instant, on oubliait l’invariance imposée par Java.
Interfaces
graphiques

Concurrence
Compléments
en POO Gérer la variance dans un programme
Aldric Degorre
2 approches principales pour prendre en compte le phénomène de la variance :
Introduction

Style • annotations de variance sur site de déclaration (n’existent pas en Java)


Objets et
classes Variance définie (définitivement) dans la déclaration du type générique.
Types et Exemple en langage Kotlin, on utilise in (contravariance) et out (covariance) :
polymorphisme

Héritage i n t e r f a c e Function< i n T, out U> { fun apply(t: T) : U }


Généricité
class A
Collections class B : A
Généricité :
introduction

Alors, dans cet exemple, Function<A, B> <: Function<B, A>. 305
Effacement de type,
variance, tableaux
Lambda-expressions
Les “streams”
Wildcards
• annotations de variance sur site d’usage
Gestion des
erreurs et
Variance choisie lors de l’usage d’un type générique (dans déclarations de variables
exceptions et signatures de méthodes).
Interfaces
graphiques C’est l’approche utilisée par Java, via le mécanisme des wildcards.
Concurrence
305. Le compilateur de Kotlin vérifie que les paramètres covariants (resp. contravariants) sont effectivement
uniquement utilisés en position covariante (resp. contravariante).
Compléments
en POO Wildcards
Aldric Degorre principe de base (1)

Introduction

Style

Objets et
classes (On revient enfin à Java !)
Types et
polymorphisme
• Quand on écrit un type paramétré, les paramètres peuvent en fait être soit des types,
Héritage
soit le symbole “?” (symbolisant un joker, un wildcard), parfois muni d’une borne.
Généricité
Collections
Généricité :
• Les types paramétrés dont le paramètre est compatible avec la borne du wildcard
se comportent alors comme des sous-types du type contenant le wildcard.
introduction
Effacement de type,
variance, tableaux
Lambda-expressions
Les “streams”
Ainsi List<Integer> est sous-type de List<?>.
Wildcards

Gestion des
erreurs et
Remarque : “?” tout seul n’est pas un type. Ce caractère ne peut être utilisé que pour
exceptions écrire un type paramétré (entre “<” et “>”).
Interfaces
graphiques

Concurrence
Compléments
en POO Wildcards
Aldric Degorre principe de base (2)

Introduction

Style • Exemple de déclaration de méthode :


Objets et
classes static double somme(List<? extends Number> liste){ ... }
Types et Cette méthode annonce pouvoir faire la somme des éléments d’une liste de
polymorphisme

Héritage
n’importe quoi, tant que ce n’importe quoi est un sous-type de nombre.
Généricité Dans ce cas, c’est équivalent à :
Collections
Généricité : static <T> double somme(List<T extends Number> liste) { ... }
introduction
Effacement de type,
variance, tableaux
Lambda-expressions
Les “streams”
• Exemple de déclarations de variables :
Wildcards

Gestion des C<? extends A> v1;


erreurs et C<? super B> v2;
exceptions

Interfaces
graphiques on peut alors affecter à v1 (resp. v2) toute valeur de type C<X> pour peu que X soit
Concurrence sous type (resp. supertype) de A (resp. B).
Exemple
Compléments
en POO Wildcards
Aldric Degorre bornes

Introduction

Style

Objets et Les wildcards peuvent être bornés. Mais différences avec les bornes de paramètre.
classes

Types et
polymorphisme
• À chaque fois que le symbole ? apparaît, on peut lui associer une borne 306 .
Héritage • Pour un ?, Java autorise une seule borne supérieure. 307
Généricité
Collections • Les ? admettent des bornes supérieures (extends), mais aussi des bornes
Généricité :
introduction
Effacement de type,
inférieures, grâce au mot-clé super, imposant que toute concrétisation doit être un
variance, tableaux
Lambda-expressions supertype de la borne.
Cependant, on ne peut pas spécifier à la fois une borne supérieure et une borne
Les “streams”
Wildcards

Gestion des
erreurs et
inférieure pour un wildcard donné.
exceptions

Interfaces
graphiques

Concurrence
306. Rappel : la borne d’un paramètre de type ne peut être spécifiée que lors de son introduction.
307. Il est possible de contourner cette limite, notamment en introduisant un type intermédiaire.
Compléments
en POO Wildcards
Aldric Degorre Vérification du type

Introduction

Style
L’affectation suivante est-elle bien typée ?
Objets et
classes List<? extends Serializable> l = new ArrayList<String>();
Types et
polymorphisme

Héritage Pour savoir, on vérifie si le terme droite de l’affectation a un type compatible avec son
Généricité emplacement.
Collections
Généricité :
introduction Son type est ArrayList<String>, or le type attendu à cet emplacement est
List<? extends Serializable> (= type de la variable à affecter).
Effacement de type,
variance, tableaux
Lambda-expressions
Les “streams”
Wildcards
• D’une part, String satisfait la borne de ? (String implémente Serializable)
Gestion des
erreurs et
exceptions
• et, d’autre part, ArrayList<String> <: List<String>.
Interfaces
graphiques
Donc cette affectation est bien typée.
Concurrence
Exemple
Compléments
en POO Wildcards
Aldric Degorre Vérification du type

Introduction

Style

Objets et
classes Généralisons :
Types et
polymorphisme
• Soit une expression expr utilisées dans un certain contexte (appel de méthode,
Héritage
affectation, ...).
Généricité
Collections
Généricité :
• Soit TE le type (déjà “converti par capture” 308 , si applicable) de expr.
introduction
Effacement de type,
variance, tableaux • Supposons que le type attendu dans le contexte soit de la forme TA<? borne>.
Lambda-expressions
Les “streams”
Wildcards
• Alors il est légal d’utiliser expr à cet endroit si et seulement si il existe un type T
Gestion des satisfaisant borne, tel que TE <: TA<T>.
erreurs et
exceptions

Interfaces
graphiques

Concurrence

308. Explication un peu plus loin. Ceci concerne le cas où le type de l’expression contient un ?.
Compléments
en POO Wildcards
Aldric Degorre utilisation pour signaler la variance

Introduction Pour revenir au problème initial, reprenons notre exemple :


Style i n t e r f a c e F u n c t i o n < T , U> { U a p p l y ( T t ) ; }
class A {}
Objets et class B extends A { }
classes c l a s s T e s t { F u n c t i o n <A , A> x ; F u n c t i o n <A , B > y ; F u n c t i o n <B , A> z ; F u n c t i o n <B , B > t ; }
Types et
polymorphisme
U → position covariante ; T → position contravariante. On “assouplit” donc Test :
Héritage
class Test2 {
Généricité
F u n c t i o n < ? s u p e r A , ? e x t e n d s A> x ; F u n c t i o n < ? s u p e r A , ? e x t e n d s B > y ;
Collections
F u n c t i o n < ? s u p e r B , ? e x t e n d s A> z ; F u n c t i o n < ? s u p e r B , ? e x t e n d s B > t ;
Généricité :
introduction }
Effacement de type,
variance, tableaux
Lambda-expressions
Les “streams” Maintenant, les affectations qu’on voulait écrire sont acceptées par le compilateur :
Wildcards
z = t ; z = x ; t = y ; x = y ; z = y ; // vé r i f i e z !
Gestion des /* e t a u s s i . . . */ A a ; B b ; a = x . a p p l y ( a ) ; b = y . a p p l y ( a ) ; a = z . a p p l y ( b ) ; b = t . a p p l y ( b ) ;
erreurs et
exceptions

Interfaces Recette : position covariante → extends, position contravariante → super.


graphiques
Se rappeler PECS : “producer extends, consumer super”.
Concurrence

Résumé Inversez super et extends et vérifiez que les appels à apply ne fonctionnent plus.
Compléments
en POO Wildcards
Aldric Degorre Conversion par capture (1)

Introduction

Style

Objets et
classes

Types et
polymorphisme En réalité, pour une expression, avoir le type Gen<?> veut dire qu’il existe 309 un type
Héritage QuelqueChose (inconnu mais fixé) tel que cette expression est de type
Généricité Gen<QuelqueChose>.
Collections
Généricité :
introduction Il faut interpréter le type d’une expression à wildcards comme un type inconnu
appartenant à l’ensemble des types respectant les contraintes trouvées.
Effacement de type,
variance, tableaux
Lambda-expressions
Les “streams”
Wildcards

Gestion des
erreurs et
exceptions

Interfaces
graphiques

Concurrence
309. Et comme on ne sait rien de ce type, vérifier que l’expression est à sa place c’est vérifier qu’elle est à sa
place pour toute valeur de QuelqueChose.
Compléments
en POO Wildcards
Aldric Degorre Conversion par capture (2)

Introduction

Style
Concrètement, lors de la vérification de type d’une expression, le compilateur effectue
Objets et
classes une opération appelée conversion par capture 310 :
Types et
polymorphisme • À chaque fois qu’un “?” apparaît au premier niveau 311 du type d’une expression 312 ,
Héritage le compilateur le remplace par un nouveau type créé à la volée, recevant un nom de
Généricité
Collections
la forme capture#i-of? (capture de wildcard).
Généricité :
introduction
Effacement de type,
• Quand une telle capture est créée, le compilateur se souvient des bornes du “?”
variance, tableaux
Lambda-expressions qu’elle remplace (il peut s’en servir dans la suite de l’analyse de types).
Les “streams”
Wildcards

Gestion des
erreurs et 310. Pour les logiciens, cette transformation s’apparente à la skolémisation : on remplace une variable
exceptions
quantifiée existentiellement par un nouveau symbole.
Interfaces 311. On ne regarde pas en profondeur : List<? extends Set<?>> devient List<capture#1-of?>, le
graphiques
compilateur se rappelant que capture#1-of? est sous-type de Set<?>.
Concurrence
312. Cela se produit quand l’expression est une variable typée avec des ?, un appel de méthode dont le type
de retour contient des ?, ou une expression castée vers un tel type.
Compléments
en POO Wildcards
Aldric Degorre Conversion par capture (3)

Introduction

Style Exemple :
Objets et
classes • soit une expression :
Types et
polymorphisme new HashMap<? super String, ? extends List<?>(),
Héritage • son type “brut” : HashMap<? super String, ? extends List<?>>,
Généricité
Collections
Généricité :
• son type après conversion par capture :
introduction
Effacement de type,
HashMap<capture#1-of?, capture#2-of?>.
variance, tableaux
Lambda-expressions
Les “streams”
• Le compilateur se rappelle que capture#1-of? :> String et que
Wildcards
capture#2-of? <: List<?>.
Gestion des
erreurs et
exceptions Cette conversion a lieu à chaque fois que le type d’une expression est évalué. Ainsi, une
Interfaces
graphiques
expression composite peut contenir plusieurs captures différentes accumulées depuis
Concurrence
l’analyse du type de ses sous-expressions.
Exemple
Compléments
en POO Wildcards
Aldric Degorre Exemple pathologique

Introduction List<? super String> l = new ArrayList<>(); l.add("toto"); //ok


l.add(l.get(0)); // Mal typé ! Mais pourquoi ?
Style

Objets et
classes Explication : à la 2e ligne,
Types et
polymorphisme • la 1e occurrence de l est de type List<capture#1-of?> =⇒ l.add(...)
Héritage
attend un paramètre de type capture#1-of? ;
Généricité
Collections • la 2e occurrence de l est de type List<capture#2-of?> (capture
indépendante !) =⇒ l.get(0) est de type capture#2-of? ;
Généricité :
introduction
Effacement de type,
variance, tableaux
Lambda-expressions
• or capture#1-of? et capture#2-of? sont, du point de vue du compilateur,
Les “streams”
Wildcards deux types quelconques sans lien de parenté, d’où l’erreur de type.
Gestion des
erreurs et On peut contourner en forçant une capture anticipée (via méthode auxiliaire) :
exceptions
// méthode auxilliaire. Ici, tout est ok, car l.get(0) de type T, or l.add() prend du T.
Interfaces
graphiques <T> static void aux(List<T> l) { l.add(l.get(0)); }
// plus loin
Concurrence
List<? super String> l = new ArrayList<>(); l.add("toto"); aux(l); // encore ok
Exemple
Compléments
en POO Wildcards
Aldric Degorre Quid des exemples plus complexes ? (1)

Introduction

Style

Objets et
classes

Types et
polymorphisme Les ? peuvent apparraître à différentes profondeurs, y compris dans les bornes :
Héritage
List<A<? super String>> las = new ArrayList<>();
Généricité
Collections
List<? extends A<? super Integer>> lar = las;
Généricité :
introduction

Pour chaque niveau de <> on vérifie que le type (resp. l’ensemble de types) donné
Effacement de type,
variance, tableaux
Lambda-expressions
Les “streams” correspond à un élémént (resp. un sous-ensemble) de l’ensemble attendu.
Wildcards

Gestion des
erreurs et
exceptions

Interfaces
graphiques

Concurrence

Détails
Compléments
en POO Wildcards
Aldric Degorre Quid des exemples plus complexes (2) ?

Introduction List<A<? super String>> las = new ArrayList<>();


Style
List<? extends A<? super Integer>> lar = las;

Objets et
classes
Dans l’exemple (2e ligne, à droite de =) :
Types et
polymorphisme
• On veut comparer List<A<? super String>> (type reçu = celui de las) et
Héritage
<? extends A<? super Integer>> (type attendu = celui de lar).
Généricité
Collections
Généricité :
• Au premier niveau, on a List et List → OK, vérifions les paramètres.
introduction
Effacement de type,
variance, tableaux
• Il faut que A<? super String> <: A<? super Integer>.
Lambda-expressions
Les “streams”
• On a A des 2 côtés... jusque là tout va bien. Vérifions l’inclusion des paramètres.
Wildcards

Gestion des
• À l’intérieur on attend “? super Integer”, mais on reçoit “? super String”.
erreurs et
exceptions • Les deux bornes sont dans le même sens, c’est bon signe.
Interfaces
graphiques
• Malheureusement, on n’a pas String :> Integer. Donc “? super String”
Concurrence n’est pas inclus dans “? super Integer” (p. ex. : le 1er ensemble contient
Détails String mais pas le 2e). Donc erreur de type !
Compléments
en POO Quelques erreurs
Aldric Degorre

Introduction

Style

Objets et • Quelques erreurs :


classes

Types et • division par 0,


polymorphisme • accès à un indice d’un tableau supérieur ou égal à la longueur de celui-ci,
Héritage • appel d’une méthode sur récepteur null,
Généricité • tentative d’affecter une valeur à une variable d’un type incompatible :
Gestion des
erreurs et
int[] t = "toto".
exceptions
Catégories d’erreurs • Grâce au typage statique de Java, la dernière erreur est détectée dès la
Exceptions
Autres solutions
Discussion et stratégies
compilation... mais, dans cette liste d’exemples, c’est la seule !
Interfaces • Que faire des autres erreurs ?
graphiques

Concurrence

Analyse
Compléments
en POO Autre scénario embêtant...
Aldric Degorre
Que se passe-t-il d’indésirable quand on exécute le programme suivant ?
Introduction
static int factorielle(int n) {
Style if (n==0) return 1;
Objets et else return n * factorielle(n-1);
classes }
Types et
polymorphisme static void main(String[] args) { System.out.println(factorielle(-2)); }
Héritage

Généricité
Quelles solutions voyez-vous, quels sont leurs propres inconvénients ?
Gestion des
erreurs et
exceptions
• tester n < 0 et retourner... quoi ?
Catégories d’erreurs
Exceptions
• un code d’erreur ? quelle valeur ? -1 ? 313
Autres solutions • et si par mégarde on appelle factorielle(-12), ne risque-t-on pas d’utiliser la
Discussion et stratégies

Interfaces
valeur -1 comme si c’était réellement une factorielle correcte et provoquer une autre
graphiques erreur bien plus tard dans l’exécution ? 314
Concurrence
→ il existe des façons plus “propres” et plus sûres de traiter ce cas !
313. ou null, si on devait retourner une référence
314. ou, dans le cas des références, appeler une méthode sur la valeur null → crash sur
Analyse NullPointerException !
Compléments
en POO Qu’est-ce qu’une erreur ?
Aldric Degorre
Les “erreurs” se manifestent à plusieurs niveaux :
Introduction

Style 1 erreur à la compilation (syntaxe, typage, ...) : le compilateur refuse de compiler le


Objets et programme, qui ne sera jamais exécuté tel quel
classes

Types et 2 crash 315 à l’exécution, avec explication (pile d’appel)


polymorphisme

Héritage
3 comportement incorrect : le programme continue à tourner ou bien termine sans
Généricité
signaler d’erreur mais ne fait pas ce qu’il est censé faire.
Gestion des
erreurs et Ces catégories sont en fait du moins grave au plus grave.
exceptions
Catégories d’erreurs
Exceptions
1 Les erreurs à la compilation se corrigent avant de livrer le produit et ne
Autres solutions
Discussion et stratégies
provoqueront jamais aucun dégât.
Interfaces
graphiques
2 Les crashs sont plus graves (se produisent dans des programmes déjà en
Concurrence
production) mais, quand ça arrive, on sait qu’il y a un bug à corriger.
3 Pire cas : le programme peut fonctionner très longtemps en faisant mal son travail
sans qu’on ne s’en aperçoive (parfois, conséquences désastreuses, cf. Ariane 5).
Analyse 315. correspond à une exception non rattrapée
Compléments
en POO Gérer les erreurs
Aldric Degorre En quoi cela consiste-t-il ?

Introduction

Style

Objets et
• Avoir une “hygiène” qui tend à faire ressortir les erreurs de programmation dès la
classes
compilation (notamment via le typage fort).
Types et
polymorphisme • Utiliser les options du compilateur (-Xlint) et des outils complémentaires
Héritage
d’analyse statique pour détecter plus d’erreurs avant exécution.
Généricité

Gestion des
• Savoir quand le composant qu’on programme génère ses propres erreurs. Dans ce
erreurs et
exceptions cas, il faut les signaler de façon propre aux client du composant et/ou fournir des
Catégories d’erreurs
Exceptions
façons de les éviter.
• Savoir réagir quand un composant qu’on utilise remonte une erreur :
Autres solutions
Discussion et stratégies

Interfaces
graphiques
• prendre en compte l’erreur et proposer un comportement (correct) alternatif,
Concurrence
• ou bien propager l’erreur (si possible en ajoutant de l’information ou en la traduisant
en tant qu’erreur du composant courant) pour qu’un client la traite.

Analyse
Compléments
en POO Techniques de gestion des erreurs
Aldric Degorre

Introduction

Style

Objets et
classes

Types et
polymorphisme Techniques proposées dans ce chapitre :
Héritage
• lancer (et rattraper) des exceptions (nous allons voir ce que c’est) ;
Généricité

Gestion des • ajouter une méthode auxiliaire pour pré-valider un appel de méthode ;
erreurs et
exceptions
Catégories d’erreurs
• utiliser un type de retour “enrichi”.
Exceptions
Autres solutions
Discussion et stratégies

Interfaces
graphiques

Concurrence
Compléments
en POO Exceptions
Aldric Degorre Description du mécanisme

Introduction

Style

Objets et Le mécanisme des “exceptions” :


classes

Types et
polymorphisme
• consiste à gérer un comportement “exceptionnel” du programme, sortant du flot de
Héritage contrôle “normal” ;
Généricité • sert quand il n’y a pas de valeur sensée à passer dans un return (la méthode est
Gestion des
erreurs et dans l’incapacité de terminer normalement 316 )
exceptions
Catégories d’erreurs
→ on ne veut pas redonner la main à l’appelant comme si rien d’anormal ne s’était
Exceptions
Le mécanisme
passé ;
Les objets exception
Autres solutions • concerne des événements “exceptionnels” = “rares” (l’exécution de ce mécanisme
Discussion et stratégies

Interfaces
est en fait coûteuse).
graphiques

Concurrence

316. Que répondriez-vous si on vous demandait : “Quel nombre réel vaut dix divisé par zéro ?”
Compléments
en POO Exceptions
Aldric Degorre Factorielle corrigée grâce aux exceptions

Introduction
class FactorielleNegativeException extends Exception {}
Style

Objets et public class Test {


classes
static int fact(int x) throws FactorielleNegativeException {
Types et if (x < 0) throw new FactorielleNegativeException();
polymorphisme
else if (x == 0) return 1;
Héritage else return x * fact(x - 1);
Généricité }

Gestion des
erreurs et public static void main(String args[]) {
exceptions System.out.println("Entrez␣un␣entier");
Catégories d’erreurs int x = (new Scanner(System.in)).nextInt();
Exceptions
Le mécanisme
try {
Les objets exception System.out.println("La␣factorielle␣est␣:␣" + fact(x));
Autres solutions
} catch (FactorielleNegative e) { // erreur détectée !
Discussion et stratégies
System.out.println("Votre␣nombre␣était␣négatif␣!");
Interfaces
}
graphiques
}
Concurrence }

Exemple
Compléments
en POO Exceptions
Aldric Degorre Explication rapide

Introduction

Style
• Signaler une exception (grâce à l’instruction throw) fait sortir de la méthode sans
Objets et
classes exécuter de return. 317
Types et
polymorphisme • L’exception peut ensuite être rattapée ou non.
Héritage
• Non-rattrapée → le programme se quitte en affichant un message d’erreur
Généricité

Gestion des • Rattrapée, si


erreurs et
exceptions • exécution sous la portée dynamique du try d’un groupe try ... catch ...
Catégories d’erreurs
Exceptions
• l’exception a le type donné entre () après le catch
Le mécanisme
Les objets exception
Autres solutions
→ exécution du bloc d’instruction de ce catch.
Discussion et stratégies

Interfaces
• La méthode contenant try ... catch ... n’est pas nécessairement celle qui
graphiques appelle directement la méthode qui fait throw (traitement non local de l’erreur).
Concurrence

317. throw = sortie exceptionnelle ; return = sortie normale


Compléments
en POO Exceptions
Aldric Degorre Non-localité du mécanisme

Introduction Supposons :
Style

Objets et • que main appelle la méthode f1 qui appelle f2 qui appelle ... qui appelle fn (→
classes
pile d’appel de méthodes avec main en bas de la pile, fn en haut)
Types et
polymorphisme
• et que fn signale une exception exn
Héritage

Généricité alors
Gestion des
erreurs et
exceptions
• si fn rattrape exn, on retrouve un fil d’exécution “normal” 318 , sinon, on sort de fn
Catégories d’erreurs
Exceptions
de façon “exceptionnelle” → alors c’est comme si l’exception se produisait dans
Le mécanisme
Les objets exception
fn-1 (propagation de l’exception).
Autres solutions
Discussion et stratégies • si fn-1 la rattrape, exécution normale du catch, sinon propagation à fn-2 etc.
Interfaces
graphiques • si l’exception est propagée jusqu’à main et main ne la rattrape pas, le programme
Concurrence se quitte en affichant l’exception 319 .
318. On exécute le catch, le finally, puis les instructions d’après.
319. Dont les informations très utiles qu’elle contient.
Compléments
en POO Exceptions
Aldric Degorre Les mots-clés et leur syntaxe complète

Introduction
• throw new MonException(...); : instruction pour signaler une exception.
Style

Objets et • try { -1- } catch ( -2- ){ -3- } ... catch ( ... ){ ... }
finally { -4- } : bloc (instruction) de traitement des exceptions.
classes

Types et
polymorphisme

Héritage • On essaye d’abord d’exécuter le bloc { -1- } du try (bloc protégé),


Généricité • Si une exception se produit, pour chaque catch, on regarde si l’exception a le type
Gestion des donné dans les ( -2- ) et on exécute le bloc { -3- } du premier catch qui
erreurs et
exceptions correspond.
Catégories d’erreurs • Enfin on exécute le bloc { -4- } du finally (dans tous les cas).
Exceptions

Le try doit toujours être suivi d’au moins une clause catch ou finally 320 .
Le mécanisme
Les objets exception
Autres solutions
Discussion et stratégies
• ... maMethode(...)throws ExceptionType { ... } : clause indiquant
Interfaces
graphiques que la méthode déclarée peut signaler 321 une exception. 322
Concurrence
320. Sauf construction try-with-resource, voir plus loin.
321. signaler ou lever (raise : mot-clé utilisé en OCaml) ou lancer/jeter (throw)
322. Cette clause est parfois obligatoire, parfois pas, détails plus loin.
Compléments
en POO Le bloc try/catch
Aldric Degorre Détails

Introduction
• Paramètre de catch = déclaration de variable d’un sous-type de Throwable.
Style

Objets et • Attention : en cas de plusieurs clauses catch, exceptions doivent être traitées
classes
dans l’ordre de sous-typage (les sous-types avant les supertypes) !
Types et
polymorphisme public class Exception1 extends Exception { }
Héritage public class Exception2 extends Exception1 { }
Généricité
try { .. }
catch (Exception2 e2) { ... }
Gestion des
catch (Exception1 e1) { ... }
erreurs et
exceptions

Si on inverse les deux catch, erreur de compilation.


Catégories d’erreurs
Exceptions
Le mécanisme
Les objets exception error: exception Exception2 has already been caught
Autres solutions
Discussion et stratégies

Interfaces Raison : Exception2 est un cas particulier de Exception1, donc déjà traité dans
graphiques
le premier catch. Le deuxième catch est alors inutile. 323
Concurrence
• Variante (“multi-catch”) : catch (Exception1 | Exception2 e){ ... }
323. Or le compilateur considère que si on écrit du code inutile, c’est involontaire et donc une erreur.
Compléments
en POO La construction try-with-resource
Aldric Degorre introduite dans Java 7, améliorée dans Java 9

Introduction try (Scanner sc = new Scanner(System.in)) {


Style System.out.println("Nom␣?"); String nom = sc.nextLine();
System.out.println("Prénom␣?"); String prenom = sc.nextLine();
Objets et
classes
identite = new Personne(nom, prenom);
}
Types et
polymorphisme

Héritage
• syntaxe : try (Resource r = ? /*expr */){ /*instr */? }
Généricité

Gestion des • équivalent : Resource r = ?; try { ? } finally { r.close(); }


erreurs et
exceptions
Catégories d’erreurs
• Assure que la ressource utilisée sera libérée après usage (avec ou sans exception).
Exceptions
Le mécanisme • Resource doit implémenter :
interface AutoCloseable { void close(); } (c’est le cas de Scanner).
Les objets exception
Autres solutions
Discussion et stratégies

Interfaces • r doit être une variable finale ou effectivement finale.


graphiques

Concurrence
• Java ≥ 9 : on peut passer comme argument de try une variable déjà déclarée 324 à
la place de la déclaration. On peut donc facilement séparer initialisation et usage.
324. si elle est finale ou effectivement finale
Compléments
en POO Exceptions
Aldric Degorre Mais au fait qu’appelle-t-on une exception ?

Introduction

Style
Remarque : on a utilisé le nom “exception” pour parler de plusieurs choses différentes...
Objets et
classes
• l’événement qui se produit quand on quitte une méthode via le mécanisme qu’on
Types et vient de décrire : “une exception vient d’être signalée” ;
polymorphisme

Héritage
(dans l’exemple, c’est ce qui se produit quand on exécute
Généricité
throw new FactorielleNegative();)
Gestion des • l’objet qui est passé du point de programme où le problème a lieu au point du
erreurs et
exceptions
Catégories d’erreurs
programme où on gère le problème ;
Exceptions
Le mécanisme
(dans l’exemple, l’objet instancié en faisant new FactorielleNegative();,
Les objets exception
Autres solutions
récupéré dans la variable e quand on fait catch (FactorielleNegative e))
Discussion et stratégies

Interfaces
• à la classe d’un tel objet.
graphiques
(dans l’exemple : la classe FactorielleNegativeException)
Concurrence

Parlons maintenant des objets et des classes !


Compléments
en POO Les objets exception
Aldric Degorre

Introduction
• Objet exception = objet passé en paramètre de throw, récupérable par catch.
Style • Instance (indirecte) de la classe Throwable.
Objets et
classes • Contient de l’information utile pour récupérer l’erreur (bloc catch) ou bien
Types et déboguer un crash, notamment :
polymorphisme

Héritage
• sa classe : le fait d’appartenir à l’une sous-classe d’exception ou une autre est déjà
Généricité
une information très utile.
• message d’erreur, expliquant les circonstances de l’erreur →
Gestion des
erreurs et String getMessage()
exceptions
Catégories d’erreurs
• cause, dans le cas où l’exception est elle-même causée par une autre exception
Exceptions
Le mécanisme
→ Throwable getCause()
Les objets exception • trace de la pile d’appels, qui donne la liste des appels imbriqués successifs (numéro
Autres solutions
Discussion et stratégies de ligne et méthode) qui ont abouti à ce signalement d’exception
Interfaces → StackTraceElement[] getStackTrace()
graphiques

Concurrence
Attention : génération de la trace → coûteuse en temps et en mémoire.
→ Une raison pour réserver le mécanisme des exceptions aux cas exceptionnels.
Compléments
en POO Classes d’exceptions
Aldric Degorre Hiérarchie d’héritage

Introduction

Style Object
Objets et
classes
extends
Types et
polymorphisme
Throwable
Héritage

Généricité extends extends


Gestion des
erreurs et Error Exception
exceptions
Catégories d’erreurs
Exceptions extends extends extends
Le mécanisme
Les objets exception
Autres solutions plein d’erreurs... RuntimeException plein d’exceptions sous contrôle...
Discussion et stratégies

Interfaces
graphiques
extends
Concurrence
plein d’exceptions hors contrôle...
Résumé
Compléments
en POO Classes d’exceptions
Aldric Degorre Description des classes proposées (1)

Introduction
• Classe Throwable :
Style
• Rôle = marqueur syntaxique pour throw, throws et catch → c’est le type de tout ce
Objets et
classes qui peut être “lancé” et rattrapé.
Types et • Après throw l’expression doit être de (super-)type Throwable.
polymorphisme
• Après throws n’apparaissent que des sous-classes de Throwable.
Héritage
• Le paramètre déclaré dans un catch a pour type un sous-type de Throwable.
Généricité

Gestion des
• Classe Error :
erreurs et
exceptions
• Indique les erreurs tellement graves qu’il n’y a pas de façon utile de les rattraper.
Catégories d’erreurs • On a le droit de les passer en argument d’un catch... mais ce n’est pas conseillé !
Exceptions
Le mécanisme • Ex. : dépassement de la capacité de la pile d’exécution (StackOverflowError),
Les objets exception
Autres solutions
Discussion et stratégies
• Classe Exception :
Interfaces • Erreurs possiblement récupérables.
graphiques
• Elles ont vocation à être passées en argument d’une clause catch.
Concurrence • Exemples :
• opération bloquante interrompue (InterruptedException).
Compléments
en POO Classes d’exceptions
Aldric Degorre Description des classes proposées (2)

Introduction • Classe RuntimeException :


Style
• Erreurs se produisant au cours d’une exécution normale 325 du “runtime” (i.e. : la JVM).
Objets et
classes
• Très souvent : erreurs évitables par de simples vérifications à l’exécution (ifs)
Types et → faire en sorte qu’elles ne se produisent pas, ne pas rattraper dans un catch !
polymorphisme Exemples :
Héritage •division par 0 (ArithmeticException),
Généricité •appel de méthode sur null (NullPointerException),
Gestion des
•accès à une case qui n’existe pas dans un tableau (ArrayOutOfBoundException),
erreurs et •tentative de cast illégale (ClassCastException)
exceptions
Catégories d’erreurs
• Mais, de plus en plus utilisées à la place des Exception normales, afin d’éviter les
Exceptions
Le mécanisme
contraintes des exceptions sous-contrôle. (throws non requis).
Les objets exception
Autres solutions
Dans ce cas : vocation a être rattrapée dans un catch malgré tout.
Discussion et stratégies

Interfaces
Remarque : l’usage qui est fait de RuntimeException est fondamentalement différent
graphiques des autres sous-classes de Exception. Ainsi, “moralement”, il est hasardeux de
Concurrence
considérer RuntimeException comme sous-type de Exception (même si c’est vrai).
325. Traduction : si erreur alors que la JVM s’exécute normalement, c’est que le programme a un bug !
Compléments
en POO Classes d’exceptions
Aldric Degorre Créer ses propres exceptions

Introduction
• Attention : avant de créer une exception, vérifiez qu’une classe d’exception
Style

Objets et
standard n’est pas déjà définie dans l’API Java pour ce cas d’erreur. 326
classes
• On crée une classe d’exception personnalisée (le plus souvent) comme sous-classe
Types et
polymorphisme d’Exception ou de RuntimeException.
Héritage
• Le String passé au constructeur est le message retourné par getMessage().
Généricité

Gestion des • Le Throwable passsé au constructeur est la valeur retournée par getCause().
erreurs et
exceptions Cf. chaînage causal des exceptions.
Catégories d’erreurs
Exceptions
Le mécanisme
• Avertissement : on ne peut pas déclarer de sous-classe générique de Throwable.
En effet, catch (comme instanceof) doit tester le type de l’exception à
Les objets exception
Autres solutions
Discussion et stratégies
l’exécution. Or à l’exécution, la valeur du paramètre n’est plus disponible 327 . Donc
Interfaces
graphiques déclarer une telle classe est inutile. Donc le compilateur l’interdit.
Concurrence
326. Très souvent, c’est en fait IllegalArgumentException ou IllegalStateException qui
conviendrait...
327. voir effacement de type
Compléments
en POO Chaîner les exceptions
Aldric Degorre

static void f1() { try { f2(); } catch (E2 e) { throw new E1(e); } }
Introduction
static void f2() { try { f3(); } catch (E3 e) { throw new E2(e); } }
Style static void f3() { throw new E3(); }
Objets et public static void main(String args[]) { f1(); }
classes

Types et
polymorphisme Quand on exécute, la JVM crashe et affiche l’exception non rattrapée (instance de E1),
Héritage ainsi que toutes ses causes sucessives.
Généricité
Exception in thread "main" exceptions.E1: exceptions.E2: exceptions.E3
Gestion des
erreurs et
at exceptions.Exn.f1(Exn.java:25)
exceptions at exceptions.Exn.main(Exn.java:42)
Catégories d’erreurs Caused by: exceptions.E2: exceptions.E3
Exceptions
Le mécanisme
at exceptions.Exn.f2(Exn.java:33)
Les objets exception at exceptions.Exn.f1(Exn.java:23)
Autres solutions
... 1 more
Discussion et stratégies
Caused by: exceptions.E3
Interfaces
at exceptions.Exn.f3(Exn.java:38)
graphiques
at exceptions.Exn.f2(Exn.java:31)
Concurrence ... 2 more
Compléments
en POO Exceptions sous contrôle vs. hors contrôle
Aldric Degorre La clause throws

Introduction

Style • Accolée à la déclaration d’une méthode, la clause throws signale qu’une exception
Objets et non rattrapée peut être lancée lors de son exécution.
classes

Types et public static void f() throws MonException {


polymorphisme // Code pouvant générer une exception de type MonException.
Héritage }
Généricité

Gestion des • Cette clause est obligatoire pour les exceptions dites “sous contrôle” 328 .
erreurs et
exceptions
Catégories d’erreurs • Conséquence : si MonException est sous-contrôle et que f() (ci-dessus) est
appelée dans une autre méthode g() qui ne la rattrape pas, alors g() doit
Exceptions
Le mécanisme

elle-même avoir throws MonException, et ainsi de suite.


Les objets exception
Autres solutions
Discussion et stratégies

Interfaces • Du coup un programme ne peut pas crasher sur une exception sous contrôle, sauf si
elle est déclarée dans la signature de main(). 329
graphiques

Concurrence

328. voir page suivante


329. En fait si... on peut tricher (ce n’est pas évident), mais c’est déconseillé !
Compléments
en POO Exceptions sous contrôle vs. hors contrôle
Aldric Degorre Qui est quoi et pourquoi ?

Introduction Deux cas :


Style

Objets et
• Les exceptions sous contrôle (checked) doivent toujours être déclarées (throws).
classes
Lesquelles ? toutes les sous-classes de Exception sauf celles de
Types et
polymorphisme RuntimeException.
Héritage
Raison : les concepteurs de Java ont pensé souhaitable 330 d’inciter fortement à
Généricité
récupérer toute erreur récupérable.
Gestion des
erreurs et • Les exceptions hors contrôle (unchecked) n’ont pas besoin d’un tel signalement.
exceptions
Catégories d’erreurs
Exceptions
Lesquelles ? uniquement les sous-classes de Error et de RuntimeException.
Le mécanisme
Les objets exception
Raisons :
Autres solutions
Discussion et stratégies
• Les Error sont considérées comme fatales : aucun moyen de continuer le
Interfaces programme de façon utile.
graphiques • Les RuntimeException peuvent se produire, par exemple, dès qu’on utilise le “.”
Concurrence d’appel de méthode, autant dire tout le temps ! Si elles étaient sous contrôle, presque
toutes les méthodes auraient throws NullPointerException !
330. Ce point est très controversé. Aucun langage notable, conçu après Java, n’a gardé ce mécanisme.
Compléments
en POO Exceptions
Aldric Degorre Remarque sur la non-localité

Introduction
• Non-localité = point fort des exceptions : en effet l’erreur n’est mentionnée que là où
Style

Objets et
elle se produit et là où elle est traitée. 331
classes
• Mais la non-localité peut être contradictoire avec l’encapsulation.
Types et
polymorphisme
→ si un composant B utilise un composant A, B doit “masquer” les exceptions de A. En
Héritage
effet : si C utilise B mais pas A, C ne devrait pas avoir affaire à A. 332
Généricité

Gestion des
• Bonne conduite : traiter toutes les erreurs de A dans B. À défaut, traduire les
erreurs et
exceptions
exceptions de A en exceptions de B (en utilisant le chaînage) :
Catégories d’erreurs
c l a s s A { p u b l i c v o i d f ( ) { t h r o w new A E x c e p t i o n ( ) ; } }
Exceptions
class B {
Le mécanisme
Les objets exception
private A a ;
Autres solutions
public void g ( ) {
Discussion et stratégies
t r y { a . f ( ) ; } c a t c h ( A E x c e p t i o n e ) { t h r o w new B E x c e p t i o n ( e ) ; }
}
Interfaces }
graphiques

Concurrence
331. Pas tout à fait vrai avec les exceptions sous contrôle.
332. Les exceptions sous contrôle rendent le problème particulièrement visible vu qu’elles sont affichées
Discussion dans la signature des méthodes, mais il existe aussi avec les exceptions hors contrôle.
Compléments
en POO Pré-valider les appels de méthode
Aldric Degorre Description de la technique

Introduction Vous avez déjà vu cette technique mise à l’œuvre dans l’API Java. Exemples :
Style

Objets et
• avec les scanners : avant de faire sc.nextInt(), il faut faire sc.hasNextInt()
classes
pour être sûr que le prochain mot lu est interprétable comme entier
Types et
polymorphisme • avec les itérateurs : avant l’appel it.next(), on vérifie it.hasNext().
Héritage

Généricité
En résumé : une méthode dont le bon fonctionnement est soumis à une certaine
Gestion des précondition peut être assortie d’une méthode booléenne retournant la validité de la
erreurs et
exceptions
précondition. En général, les deux méthodes ont des noms en rapport :
Catégories d’erreurs
Exceptions
Autres solutions
• void doSomething()/boolean canDoSomething()
Discussion et stratégies
• Foo getFoo()/hasFoo()
Interfaces
graphiques
Le même test doit malgré tout être laissé au début de doSomething() :
Concurrence
if (!canDoSomething()) throw new IllegalStateException(); // ou IllegalArgumentException

(→ si on oublie de tester canDoSomething au pire, crash, au lieu de résultat absurde)


Compléments
en POO Pré-valider les appels de méthode
Aldric Degorre Exemple

Introduction

Style

Objets et
classes Pour la factorielle, on peut créer une méthode de validation du paramètre, mais le plus
Types et simple c’est encore de penser à tester x ≥ 0 avant de l’appeler.
polymorphisme

Héritage La méthode factorielle s’écrira elle-même avec ce test et une exception hors contrôle :
Généricité
static int fact(int x) { // pas de throws
Gestion des if (x < 0)
erreurs et
exceptions
throw new IllegalArgumentException(); // hors contrôle
Catégories d’erreurs else if (x == 0)
Exceptions return 1;
Autres solutions
Discussion et stratégies
else
return x * fact(x - 1);
Interfaces
graphiques }

Concurrence

Exemple
Compléments
en POO Enrichir le type de retour
Aldric Degorre

Introduction
Il s’agit d’une amélioration de la technique du “code d’erreur” : au lieu de réserver une
Style valeur dans le type, on prend un type somme, “plus large”
Objets et
classes • contenant, sans ambiguïté, les vraies valeurs de retour et les codes d’erreur,
Types et
polymorphisme • et tel qu’on ne puisse pas utiliser la valeur de retour directement sans passer par un
Héritage getter “fait pour ça”.
Généricité

Gestion des Possibilités :


erreurs et
exceptions
Catégories d’erreurs
• programmer un “type somme” à la main
Exceptions
Autres solutions
Discussion et stratégies
• s’il n’y a qu’un seul code d’erreur (ou bien si on ne veut pas distinguer les
Interfaces différentes erreurs), utiliser la classe Optional<T> (Java 8).
graphiques

Concurrence
• si la méthode qui produit l’erreur devait être void, retourner à la place un boolean
(si on ne souhaite pas distinguer les erreurs) ou mieux, une valeur de type énuméré
(chaque constante correspond à un code d’erreur).
Compléments
en POO Enrichir le type de retour
Aldric Degorre Exemple 1

Introduction
Toujours avec la factorielle, avec le type Optional :
Style

Objets et static Optional<Integer> fact(int x) { // pas de throws


classes if (x < 0) return Optional.empty();
Types et else if (x == 0) return Optional.of(1);
polymorphisme else return Optional.of(x * fact(x - 1).get());
Héritage
}

Généricité

Gestion des Comme cette méthode ne retourne pas un int (ou Integer), on n’est pas tenté
erreurs et
exceptions d’utiliser la valeur de retour directement.
Catégories d’erreurs
Exceptions
Autres solutions
Pour utiliser la valeur de retour :
Discussion et stratégies

Interfaces
Optional<Integer> result = fact(x);
graphiques if (result.isPresent()) System.out.println(x + "!␣=␣" + result.get());
else System.out.println(x + "␣n'a␣pas␣de␣factorielle␣!'");
Concurrence

Autre possibilité : result.ifPresent(f -> System.out.println(f)); 333


333. opérateur “->” introduit dans Java 8, sert à dénoter une valeur fonctionnelle. Hors programme !
Compléments
en POO Enrichir le type de retour
Aldric Degorre Exemple 2

Introduction
public class Mail {
Style

Objets et /* attributs, constructeurs, etc. */


classes

Types et public static enum SendOutcome { OK, AUTH_ERROR, NO_NETWORK, PROTOCOL_ERROR }


polymorphisme

Héritage
/*
send() : envoie le mail. Sans les erreurs, void suffirait...
Généricité
... mais évidemment des erreurs sont possbles.
Gestion des Différents codes sont prévus dans l'enum SendOutCome
erreurs et */
exceptions
Catégories d’erreurs
Exceptions public SendOutcome send() {
Autres solutions
Discussion et stratégies
/*
essaye d'envoyer le mail, mais interrompt le traitement
Interfaces
graphiques
avec "return error.TRUC;" dès qu'il y a un souci
*/
Concurrence
return SendOutcome.OK;
}
}
Exemple
Compléments
en POO Points forts et limites de chaque approche
Aldric Degorre Exceptions (1)

Introduction

Style • Toutes les exceptions :


Objets et • L’instanciation d’un Throwable est coûteux (génération de la pile d’appel, etc.)
classes
• L’exécution de throw est coûteuse (proportionnelle à la hauteur de pile d’appel à
Types et
polymorphisme remonter pour arriver au catch).
Héritage Cependant : ce coût est inévitable pour pouvoir obtenir un traitement non local 334 .
Généricité
• Exceptions hors contrôle :
Gestion des
erreurs et
exceptions
• on peut oublier de les prendre en compte (→ crash, alors que le programme n’aurait
Catégories d’erreurs pas dû compiler du tout si l’exception avait été sous contrôle)
Exceptions

→ idéales, soit pour les erreurs à traitement non local 335 , soit pour les erreurs pour
Autres solutions
Discussion et stratégies

Interfaces lesquelles il est acceptable de laisser crasher le programme.


graphiques

Concurrence
334. En effet : quel que soit le mécanisme utilisé, un traitement “non local” signifie qu’on doit remonter un
nombre arbitraire de niveaux dans la pile d’appels.
335. À condition de ne pas oublier de toutes les traiter ! À cet effet, penser à documenter les méthodes qui
Discussion lèvent de telles exceptions → mot-clé @throws de la JavaDoc.
Compléments
en POO Points forts et limites de chaque approche
Aldric Degorre Exceptions (2)

Introduction
• Exceptions sous contrôle :
Style
• “Pollution syntaxique” : obligation d’ajouter au choix des throws ou des try dans
Objets et
classes toute méthode ou une telle exception peut se produire.
Types et
• Si on sait traiter l’exception localement, pas de problème. 336
polymorphisme • Sinon on se retrouve à ajouter une clause throws à l’appelant (qui vient s’ajouter aux
Héritage autres s’il y en avait déjà... ), puis à l’appelant de l’appelant, puis ... 337
Généricité • Incitation à faire des try catch juste pour éviter d’ajouter un throws : nuisible si
Gestion des on fait taire un problème sans le traiter. P. ex. :
erreurs et
exceptions v o i d f ( ) t h r o w s Ex1 { t h r o w new Ex1 ( ) ; }
Catégories d’erreurs
void g ( ) {
Exceptions
t r y { f ( ) ; } c a t c h ( Ex1 e ) { / * r i e n ! <−− ouh , pa s b i e n ! * / }
Autres solutions
}
Discussion et stratégies

Interfaces
graphiques → la pratique moderne semble éviter de plus en plus l’utilisation de ce mécanisme.
Concurrence Cependant l’API Java l’utilise beaucoup et il faut donc savoir comment faire avec.
336. Mais pour un traitement local, à quoi bon utiliser les exceptions ?
337. Modification de signature des appelants qui finalement revient à peu près à changer les types de retour
Discussion par des types enrichis. → critique courante, dans ce cas, qu’apportent les exceptions sous-contrôle en plus ?
Compléments
en POO Points forts et limites de chaque approche
Aldric Degorre Méthodes auxiliaires

Introduction

Style • Si vérifier les paramètres est aussi coûteux qu’effectuer l’opération, un appel
Objets et sécurisé devient deux fois plus long.
classes

Types et
→ réserver aux cas où la vérification a priori est peu coûteuse
polymorphisme
• Le compilateur ne sait pas vérifier qu’on a appelé et testé canDoSomething()
Héritage

Généricité
avant d’appeler doSomething().
Gestion des → problèmes potentiellement reportés à l’exécution.
erreurs et
exceptions • Parfois (rarement), on ne sait pas quoi faire quand l’appel à canDoSomething()
Catégories d’erreurs
Exceptions
Autres solutions
retourne false. Peut-être que seul l’appelant de l’appelant de l’appelant de...
Discussion et stratégies l’appelant connait suffisament de contexte pour traiter le cas d’erreur.
Interfaces
graphiques → (relativement) peu adapté au traitement d’erreur non local 338
Concurrence
338. On peut propager de la façon suivante : l’opération void doFoo() appelle void doBar(), mais
doBar() a une méthode de vérification boolean canBar(), alors on peut écrire une méthode de
vérification boolean canFoo() pour l’opération doFoo(), qui appellera canBar().
Discussion Mais ce n’est quand-même pas pratique !
Compléments
en POO Points forts et limites de chaque approche
Aldric Degorre Type de retour enrichi

Introduction

Style • “Pollution syntaxique” : il est bien plus concis et clair de travailler sur des int que
Objets et
classes
des Optional<Integer> (déclarations plus courtes, pas de méthodes spéciales
Types et pour accéder aux valeurs).
polymorphisme

Héritage
• La “pollution syntaxique” apparait dans les signatures de toutes les méthodes de la
Généricité pile jusqu’à celle qui traite le cas d’erreur
Gestion des → à réserver de préférence pour erreurs à traitement local.
erreurs et
exceptions
Catégories d’erreurs
• Perte d’efficacité par rapport à valeur directe : on crée un nouvel objet à chaque fois
Exceptions
Autres solutions
qu’on retourne de la méthode.
→ à réserver pour situation où cas d’erreur aussi fréquent que cas “normal” (en
Discussion et stratégies

Interfaces
graphiques comparaison : throw new E(); coûteux mais ok si rare).
Concurrence

→ cette technique pose les mêmes problèmes que les exceptions sous contrôle pour les
erreurs traitées non localement, mais peut se révéler plus efficace localement.
Discussion
Compléments
en POO Points forts et limites de chaque approche
Aldric Degorre Synthèse des critères

Introduction

Style

Objets et Pas toujours de choix unique et idéal quant à la stratégie de gestion d’une erreur. Les
classes
critères suivants, avec leurs exigences contradictoires, peuvent être considérés :
Types et
polymorphisme

Héritage
• localité du traitement de l’erreur (local, non local ou pas de traitement) ;
Généricité • coût de la vérification de la validité de l’opération avant de l’effectuer ;
Gestion des
erreurs et • sûreté, c.-à-d. obligation de traiter le cas d’erreur (contre-partie : pollution
exceptions
Catégories d’erreurs syntaxique) ;
Exceptions
Autres solutions
Discussion et stratégies • fréquence de l’erreur (totalement évitable en corrigeant le programme, rare,
Interfaces fréquente, ...) ;
graphiques

Concurrence • qualité de l’encapsulation requise.

Discussion
Compléments
en POO Interfaces graphiques en Java
Aldric Degorre

Pour des raison historiques, plusieurs bibliothèques sont utilisables (y compris dans le
Introduction
JDK) :
Style

Objets et
classes
• AWT : existe depuis les premières versions de Java, se repose sur les composants
Types et graphiques “natifs” du système d’exploitation (rapide, mais apparence différente
polymorphisme
entre Windows, macOS, Linux, etc... )
Héritage

Généricité • Swing : bibliothèque “officielle” de Java. Dépend peu des composants du système
Gestion des (donc apparence différente entre plateformes).
erreurs et
exceptions
• SWT (et surcouche JFace) : bibliothèque du projet Eclipse. Se repose
Interfaces
graphiques principalement sur les composants natifs (comme AWT) mais implémente tout ce
Interfaces graphiques
Principes que le système ne fournit pas.
JavaFX
Stratégies • JavaFX : alternative plus moderne, similaire à Swing dans les principes. 339
Concurrence

Dans ce cours : exemple de JavaFX, mais principes similaires pour les autres.
339. Intégrée un temps au JDK comme potentiel successeur de Swing (Java 8), puis finalement confiée au
projet OpenJFX (Java 11).
Compléments
en POO Interfaces graphiques en Java
Aldric Degorre Avertissement

Introduction

Style

Objets et
classes

Types et
polymorphisme
Attention, c’est un sujet très vaste, même en se limitant à JavaFX.
Héritage

Généricité Ce cours ne fera qu’effleurer certains des sujets essentiels et donner quelques pointeurs
Gestion des pour mener à bien le projet.
erreurs et
exceptions
Il conviendra d’avoir une démarche active pour combler d’éventuels besoins non
Interfaces
graphiques couverts par le cours (consultez les pages de documentation de Java !!!).
Interfaces graphiques
Principes
JavaFX
Stratégies

Concurrence
Compléments
en POO Principes généraux
Aldric Degorre

Introduction

Style

Objets et
classes • Interface graphique : hiérarchie de composants graphiques se contenant les uns
Types et les autres (ex : un bouton dans un panneau dans une fenêtre... )
polymorphisme

Héritage • Quelques composants standard (fournis par l’API), mais en général on aime bien les
Généricité personnaliser. (ex : l’API fournit la fenêtre de base, mais on peut définir une fenêtre
Gestion des
erreurs et
“éditeur” qu’on va instancier à volonté)
exceptions
• Les composants peuvent capter des évènements (validation, clic souris, entrée
Interfaces
graphiques clavier, redimensionnement, ... ), dont le traitement est délégué à des fonctions de
Interfaces graphiques
Principes rappel (gestionnaires d’évènements) → programmation évènementielle
JavaFX
Stratégies

Concurrence
Compléments
en POO Construire une GUI : mode d’emploi (1)
Aldric Degorre

Introduction

Style
Pour une fenêtre “statique” :
Objets et
classes 1 Conceptualisez d’abord la fenêtre de votre application, dessin à l’appui.
Types et
polymorphisme
2 Déterminez ses composants et leur hiérarchie (qui contient qui ?) sous forme
Héritage d’arbre.
Généricité
3 Programmez/écrivez 340 la description de la fenêtre, de ses composants et de leurs
Gestion des
erreurs et propriétés (taille, couleur, etc.) et des relations entre composants (notamment
exceptions
relations contentant/contenu).
Interfaces
graphiques
Interfaces graphiques À ce stade, votre programme peut afficher votre jolie fenêtre... qui ne fera rien 341 . Pour
en faire une application utile, il faut maintenant associer des actions aux évènements.
Principes
JavaFX
Stratégies

Concurrence

340. En fonction du contexte, ça peut être un programme Java... ou bien un fichier dans un langage
descriptif tel que HTML ou FXML.
341. On a en fait implémenté la partie “Vue” du patron MVC.
Compléments
en POO Construire une GUI : mode d’emploi (2)
Aldric Degorre Gérer les évènements

Introduction
Pour gérer les évènements 342 :
Style

Objets et
classes
1 On crée des gestionnaires d’évènement, spécifiques à chaque type d’évènement.
Types et
polymorphisme
2 Pour chaque type d’évènement qu’on veut traiter sur un composant donné, on lui
Héritage associe un gestionnaire, en le passant à une certaine méthode de ce composant.
Généricité (Ceci peut se faire lors de l’initialisation du composant en question.)
Gestion des
erreurs et 3 Désormais, à chaque fois que cet évènement se produira, le gestionnaire sera
exceptions
exécuté avec pour paramètre un descripteur d’évènement.
Interfaces
graphiques
Interfaces graphiques
Principes
Un gestionnaire d’évènement est une “fonction” 343 dont le paramètre est un descripteur
JavaFX
Stratégies d’évènement (de type souvent nommé XXXEvent), contenant la description des
Concurrence circonstances de l’évènement (composant d’origine, coordonnées, bouton cliqué ...).
342. Ceci correspond à la partie “Contrôleur” du patron MVC.
343. Fonction de rappel, matérialisée comme un objet contenant un méthode qui décrit la fonction ; c’est
donc une fonction de première classe.
Compléments
en POO JavaFX
Aldric Degorre

Introduction • A existé tantôt comme bibiliothèque séparée, tantôt comme composant du JDK
Style
(Java 8 à 10). À partir de Java 11, développé au sein du projet OpenJFX.
Objets et
classes
• Avant Java 8, JavaFX pouvait être programmé via un langage de script appelé
Types et
polymorphisme JavaFX Script, celui-ci a été abandonné depuis.
Héritage
• JavaFX : bien plus qu’une bibiliothèque de description d’IG.
Généricité

Gestion des
Par exemple, en plus des composants typiques, on peut insérer dans l’arbre des
erreurs et
exceptions
formes 3D, et appliquer au tout diverses transformations géométriques, y compris
Interfaces
en 3D.
graphiques
Interfaces graphiques
Principes Une particularité de JavaFX, c’est la possiblitié de décrire une IG via un langage de
description appelé FXML (inspiré de HTML), et de définir le style des composants via des
JavaFX
Stratégies

Concurrence
pages de style CSS. 344

344. Ce cours n’explique pas la syntaxe de FXML et de CSS, mais seulement de la construction de l’IG via
des méthodes purement Java. Cependant, vous pouvez les utiliser en TP ou en projet.
Compléments
en POO Structure d’une IG en JavaFX
Aldric Degorre

Introduction
En JavaFX, nous avons la hiérarchie suivante :
Style
• L’arbre est appelé graphe de scène (scene graph) et est constitué de nœuds,
Objets et
classes instances de sous-classes de Node.
Types et
polymorphisme • Les nœuds internes sont instances de sous-classes de Parent.
Héritage
• Le nœud racine de l’arbre est associé à un objet de classe Scene. La scène
Généricité
correspond à la totalité de l’IG destinée à effectuer une tâche donnée.
Gestion des
erreurs et
exceptions
• La scène, pour être affichée, doit être donnée à un objet de classe Stage 345 (le lieu
Interfaces où sera dessinée l’IG ; p. ex., sur un ordinateur de bureau : une fenêtre).
graphiques

Cette organisation permet de facilement changer le contenu entier d’une fenêtre pour
Interfaces graphiques
Principes

passer d’une tâche à l’autre : il suffit de dire au stage d’afficher une autre scene.
JavaFX
Stratégies

Concurrence
345. scene et stage : les deux se traduisent en Français par “scène” mais ont un sens très différent. Scene
désigne une subdivision temporelle (scène = chapitre d’une pièce de théâtre), alors que stage désigne un
lieu (scène = les planches sur lesquelles on joue la pièce).
Ainsi, pour éviter les confusions, soit je ne traduirai pas stage soit je dirai juste... une fenêtre !
Compléments
en POO Exécution de l’IG en JavaFX
Aldric Degorre

Introduction 1 Pour des raisons techniques 346 , la construction ne peut être faite directement
Style
depuis main() où une méthode appelée par main().
Objets et
classes 2 À la place, on doit créer une classe MonAppJFX extends Application, pour
Types et
polymorphisme laquelle il faudra implémenter la méthode void start(Stage stage).
Héritage C’est dans cette méthode qu’on initie la construction.
Généricité
3 Pour démarrer l’interface graphique (par exemple depuis main()) on fait : 347
Gestion des
erreurs et
exceptions Application.launch(MonAppJFX.class);

Interfaces
graphiques
Interfaces graphiques
ou bien, dans le cas où on fait l’appel depuis MonAppJFX, juste :
Principes
JavaFX Application.launch();
Stratégies

Concurrence

346. Des histoires de threads dont nous reparlons juste après.


Cette contrainte n’est pas spécifique à JavaFX, mais inhérente à la programmation évènementielle.
347. Cette instruction lance la méthode start() dans le thread des évènements JavaFX.
Compléments
en POO Gérer les évènements en JavaFX
Aldric Degorre

Introduction
Pour gérer les évènements,
Style
1 les gestionnaires d’évènement de JavaFX implémentent l’interface
Objets et
classes EventHandler<T> :
Types et
polymorphisme public interface EventHandler<T extends Event> {
void handle (T e);
Héritage
}
Généricité

Gestion des
erreurs et (où T peut être remplacé par le type d’évènement à traiter, ex :
exceptions
ActionEvent, KeyEvent, MouseEvent, ...).
Interfaces
graphiques
Interfaces graphiques
2 on associe un gestionnaire d’évènement à un composant JavaFX ainsi :
Principes
JavaFX
composant.setOnXXXX(gestionnaire) (ex : void
Stratégies
setOnMousePressed(EventHandler<? super MouseEvent> gest))
Concurrence
3 à partir de désormais, quand un évènement du type indiqué se produit, la méthode
handle() du gestionnaire est exécutée.
Compléments
en POO Gérer les évènements en JavaFX
Aldric Degorre exemple

Introduction

Style
Prenons l’exemple d’ActionEvent. Je peux par exemple créer la classe :
Objets et public class GererEnregistrement implements EventHandler<ActionEvent> {
classes private final Document doc;
Types et public GererEnregistrement(Document d) { this.doc = d; }
polymorphisme public void handle(ActionEvent e) { d.enregistre(); }
Héritage }
Généricité

Gestion des
erreurs et
Supposons maintenant que la variable boutonEnregistrer désigne un composant de
exceptions type Button, alors pour que cliquer sur le bouton désormais déclenche l’enregistrement,
Interfaces
graphiques
il suffit d’ajouter l’instruction :
Interfaces graphiques
Principes boutonEnregistrer.setOnAction(new GererEnregistrement(documentCourant));
JavaFX
Stratégies

Concurrence Remarque : si e objet évènement, alors e.getSource() référence le composant où


l’évènement a été créé. Cette référence peut servir faire un traitement différencié en
fonction de l’état du composant.
Compléments
en POO Gérer les évènements
Aldric Degorre Utilisez les lambda-expressions !

Introduction

Style EventHandler étant une interface fonctionnelle, lambda-expressions possibles :


Objets et
classes Pour les gestionnaires courts, préférer les lambda-abstractions :
Types et
polymorphisme composant.setOnMousePressed(e -> { /* gérer l'évènement e ici */ });
Héritage

Généricité
Pour les gestionnaires longs, écrire un méthode et en passer une référence :
Gestion des
erreurs et void methodeDuClic(MouseEvent e) { /* gérer l'évènement e ici */ }
exceptions
...
Interfaces composant.setOnMousePressed(controleur::methodeDuClic); // référence de méthode
graphiques
Interfaces graphiques
Principes
JavaFX
Stratégies
Dernière technique intéressante si programme plus long qu’un simple exemple : la partie
Concurrence
où on associe composants et gestionnaires est plus succincte et claire. En plus, on peut
regrouper les méthodes de gestion d’événement dans dans une même classe 348 .

348. le contrôleur du patron MVC


Compléments
en POO Exécution des évènements
Aldric Degorre Parler de threads sans en parler.

Introduction
• Les méthodes handle() des gestionnaires d’évènement s’exécutent les unes
Style

Objets et
après les autres (jamais en même temps).
classes

Types et
• De même, ces gestionnaires commencent à s’exécuter seulement après la méthode
polymorphisme start() de l’Application (et sa pile d’appels).
Héritage

Généricité
• En revanche, la méthode main() (et sa pile d’appels) continue à s’exécuter en
Gestion des parallèle → ne jamais tenter de modifier/ajouter un composant JavaFX depuis
erreurs et
exceptions
main() ou une méthode appelée par main() (résultats imprévisibles).
Interfaces
graphiques
• Remarque : un traitement long 349 ou un appel bloquant dans un gestionnaire peut
Interfaces graphiques
Principes
donc ralentir ou bloquer toute l’application.
JavaFX
Stratégies
Si on a besoin d’exécuter du code long ou bloquant, il faudra le lancer en parallèle
Concurrence sur un autre thread (concept de worker thread 350 ).
349. i.e. plus long que l’intervalle de rafraîchissement de la fenêtre
350. Vous pouvez, à cet effet, créer un thread classique ou, mieux, utiliser javafx.concurrent.Worker,
l’API prévue par JavaFX, ou bien toute autre API de votre convenance.
Compléments
en POO Threads en JavaFX
Aldric Degorre
Pour parler “threads”, ainsi 3 sortes de threads dans un programme JavaFX :
Introduction

Style • 1 thread initial (main) : dans une application JavaFX, ne sert qu’à démarrer le
Objets et JFXAT, via l’appel à Application.launch() en plaçant un évènement initial
classes

Types et
(l’exécution de start()) dans la file d’attente des évènements.
polymorphisme
• 1 thread d’application JavaFX (JFXAT) qui lance les tâches de sa file d’attente
Héritage
(typiquement, les gestionnaires d’évènement).
Généricité
• les gestionnaires d’événements sont automatiquement programmés sur le JFXAT
Gestion des
erreurs et • ajout possible de tâches depuis autre thread avec Platform.runLater(task).
exceptions
• Intérêt du JFXAT : permettre à l’IG de tourner indépendamment du reste du
Interfaces
graphiques programme, en restant réactive.
Interfaces graphiques • Pourquoi restreindre toute manipulation de l’IG à ce seul JFXAT : on évite les
Principes
JavaFX problèmes usuels 351 du multithreading en rendant les exécutions de gestionnaires
atomiques 352 les unes par rapport aux autres.
Stratégies

Concurrence

• parfois des worker threads pour les tâches longues ou bloquantes.


351. Entrelacements indésirables, accès en compétition, ...(cf. chapitre sur la concurrence)
352. Elles ne s’interrompent pas les unes les autres.
Compléments
en POO Exemple JavaFX minimaliste
Aldric Degorre

Introduction import javafx.application.Application; import javafx.scene.*; import


Style
javafx.scene.control.*; import javafx.scene.layout.BorderPane;

Objets et
classes
public class JFXSample extends Application {
@Override public void start(Stage stage) throws Exception {
Types et
MenuItem exit = new MenuItem("Exit"); exit.setOnAction(e -> System.exit(0));
polymorphisme
Menu file = new Menu("File"); file.getItems().add(exit);
Héritage MenuBar menu = new MenuBar(); menu.getMenus().add(file);
Généricité Label lbl = new Label("Exemple␣de␣Label␣qui␣tourne...");
Gestion des
lbl.setOnMousePressed(e -> lbl.setRotate(lbl.getRotate() + 10));
erreurs et BorderPane root = new BorderPane(); root.setTop(menu); root.setCenter(lbl);
exceptions Scene scene = new Scene(root, 400, 400);
Interfaces stage.setScene(scene); // définir la scène à afficher
graphiques stage.setTitle("Application␣test");
Interfaces graphiques
Principes
stage.show(); // lancer l'affichage !
JavaFX }
Stratégies

Concurrence public static void main(String[] args) { Application.launch(args); }


}

Exemple
Compléments
en POO Catalogue de composants courants de JavaFX
Aldric Degorre

Introduction

Style • Vous en avez vus quelques uns dans les exemples de ce cours.
Objets et
classes • Pour plus d’exemples, le mieux, c’est d’ouvrir les tutoriels sur le site d’Oracle.
Types et
polymorphisme
• à propos de l’utilisation des contrôles les plus communs en JavaFx :
Héritage https://docs.oracle.com/javase/8/javafx/
Généricité user-interface-tutorial/ui_controls.htm.
Gestion des
• à propos des Pane et gestionnaires de disposition (layouts) : http://docs.
erreurs et oracle.com/javase/8/javafx/layout-tutorial/builtin_layouts.htm
exceptions
(la classe Pane sert à contenir d’autres sous-composants ; ses classes dérivées
Interfaces
graphiques gèrent, chacune à leur façon, la manière d’agencer les nœuds enfants.)
Interfaces graphiques
Principes

Attention quand vous trouvez de la documentation : vérifiez qu’elle concerne bien la


JavaFX
Stratégies

Concurrence version installée chez vous. 353 .

353. Les moteurs de recherche tendent encore trop à réferencer d’anciennes versions comme JavaFX 2...
Compléments
en POO Organiser une application graphique
Aldric Degorre Séparation vue et modèle

Introduction

Style
C’est souvent une bonne idée de séparer les deux aspects suivants :
Objets et
classes
• Modèle : cœur du programme, partie “métier”. C’est ici que sont gérées, organisées,
Types et traitées les données. On y trouve les déclarations de structures de données ainsi
polymorphisme
que les méthodes implémentant les différents algorithmes traitant sur ces
Héritage
structures.
Généricité

Gestion des • Vue : partie qui sert à présenter l’application à l’utilisateur et sur laquelle
erreurs et
exceptions l’utilisateur agit.
Interfaces
graphiques
Interfaces graphiques
Idéalement, les classes du modèle ne dépendent pas des classes de la vue 354 .
Principes
JavaFX Plusieurs stratégies pour coordonner M et V, notamment : Model-View-Controller (MVC),
Stratégies

Concurrence
Model-View-Presenter (MVP) et Model-View-ViewModel (MVVM) (dans les 3 cas : ajout
d’un 3e composant).
354. ce qui permet de changer la présentation de l’application, notamment pour la porter sur des
plateformes différentes
Compléments
en POO Organiser une application graphique
Aldric Degorre L’architecture MVC

Introduction

Style

Objets et
classes Architecture MVC décrite depuis 1978... encore très populaire pour les applications
Types et graphiques, notamment les applications web. Le troisième composant est le contrôleur.
polymorphisme

Héritage
• Modèle : déjà décrit.
Généricité

Gestion des
• Vue : déjà décrite.
erreurs et
exceptions • Contrôleur : partie du programme servant à interpréter les évènements (entrées de
Interfaces
graphiques
l’utilisateur dans la vue, mais pas seulement) pour agir sur le modèle (déclencher
Interfaces graphiques
Principes
un traitement, ...) et la vue (ouvrir un dialogue, ...).
JavaFX
Stratégies

Concurrence
JavaFX est particulièrement adapté à mettre en œuvre une stratégie MVC.
Compléments
en POO Illustration
Aldric Degorre

Introduction
est observé par
Style Modèle : Vue :
Objets et • données de l’application • composants de
classes

Types et • algorithmes de l’application interface utilisateur


polymorphisme

Héritage

Généricité met à jour 355


Gestion des
erreurs et
exceptions met à jour et lance envoie ses évènements à
Interfaces
graphiques
des traitements
Interfaces graphiques
Principes
JavaFX
Contrôleur :
• gestionnaires d’événenements
Stratégies

Concurrence

355. pour la partie de l’interface utilisateur qui ne sert pas à la présentation des données, par exemple :
ouverture des menus, boîtes de dialogue, etc.
Compléments
en POO Le patron de conception Observateur/Observable
Aldric Degorre En général

Introduction

Style

Objets et
classes • Cas d’application : quand les changements d’état d’un objet donné (l’observable)
Types et
polymorphisme
peut avoir des répercussions sur de multiples autres objets (les observateurs).
Héritage • Principe : Chaque observable contient une liste d’observateurs abonnés. Quand un
Généricité
changement a lieu, il appelle sur chaque élément de cette liste une même méthode
Gestion des
erreurs et (d’une interface commune) pour prévenir tous ses observateurs.
exceptions

Interfaces
• Intérêt : éviter que la classe de cet objet ne dépende des classes de ses observeurs
graphiques
Interfaces graphiques
(la dépendance se créée entre objets, à l’exécution).
Principes
JavaFX • Patron utilisé pour la relation Modèle ↔ Vue dans MVC : V réagit aux changements
Stratégies

Concurrence
de M, sans que celui-ci n’ait de dépendance vers la seconde.
Compléments
en POO Le patron de conception Observateur/Observable
Aldric Degorre Avec java.util

Introduction

Style

Objets et
classes Implémentation “historique” de Java : interface java.util.Observer et classe
Types et
polymorphisme
java.util.Observable. Regardez leurs documentations.
Héritage
En plus de ce qu’on vient de décrire, java.util.Observable contient un mécanisme
Généricité
pour éviter de notifier tout le temps les observateurs, via le couple de méthodes
Gestion des
erreurs et setChanged()/hasChanged() :
exceptions

Interfaces • setChanged() : sert à signaler que l’observable vient d’être modifié ;


graphiques
Interfaces graphiques
Principes
• hasChanged() : renvoie true si l’observable a été modifié depuis la dernière
notification aux observateurs.
JavaFX
Stratégies

Concurrence
Compléments
en POO Le patron de conception Observateur/Observable
Aldric Degorre Avec javafx.beans (1)

Introduction

Style

Objets et
JavaFX a sa propre implémentation de ce patron.
classes
Les propriétés des composants graphiques (mais pas seulement) sont matérialisées par
Types et
polymorphisme des instances de javafx.beans.Property, sous-interface de
Héritage javafx.beans.Observable.
Généricité

Gestion des
Pour toute propriété de nom “value”, le composant contiendra les 3 méthodes
erreurs et
exceptions
suivantes :
Interfaces
graphiques • public Double getValue() : lire la valeur de la propriété
Interfaces graphiques
Principes
JavaFX
• public void setValue(Double value) : modifier la valeur de la propriété
Stratégies

Concurrence
• public DoubleProperty valueProperty() : obtenir l’objet-propriété
lui-même
Compléments
en POO Le patron de conception Observateur/Observable
Aldric Degorre Avec javafx.beans (2))

Introduction

Style

Objets et
classes

Types et Comme ces propriétés sont observables, on peut leur ajouter des observateurs :
polymorphisme

Héritage slider.valueProperty().addListener(
(observable, oldVal, newVal) -> afficheur.textProperty().setValue("valeur␣:␣" +
Généricité
newVal));
Gestion des
erreurs et
exceptions
Remarque : les composants graphiques contiennent de telles propriétés (cf. exemple),
Interfaces
graphiques mais elles peuvent aussi être utilisées dans toute autre classe, notamment dans la partie
Modèle de l’application, permettant l’observation du Modèle par la Vue.
Interfaces graphiques
Principes
JavaFX
Stratégies

Concurrence
Compléments
en POO Concurrence
Aldric Degorre Quoi ?

Introduction

Style
Definition (Concurrence)
Objets et
classes
Deux actions, instructions, travaux, tâches, processus, etc. sont concurrents si leurs
Types et
polymorphisme exécutions sont indépendantes l’une de l’autre (l’un n’attend pas de résultat de l’autre).
Héritage

Généricité

Gestion des
• Conséquence : deux actions concurrentes peuvent s’exécuter simultanément, si la
erreurs et
exceptions
plateforme d’exécution le permet.
Interfaces • Un programme concurrent est un programme dont certaines portions de code sont
graphiques

Concurrence indépendantes les unes des autres et tel que la plateforme d’exécution sait 356
Introduction
Concurrence et
exploiter ce fait pour optimiser l’exécution. 357 .
parallélisme
Les abstractions
Threads en Java

356. Le plus souvent, cette connaissance nécessite que les portions concurrentes soient signalées dans le
Dompter le JMM
APIs de haut niveau

code source.
357. Notamment en exécutant simultanément ces portions de code si c’est possible.
Compléments
en POO Concurrence
Aldric Degorre Où et quand ?

Introduction

Style • Naturelle et nécessaire dans des situations variées en programmation 358 :


Objets et
classes
• Serveurs web : un même serveur doit pouvoir servir de nombreux clients
Types et indépendamment les uns des autres sans les faire attendre.
polymorphisme • Interfaces homme-machine : le programme doit pouvoir, tout en prenant en compte,
Héritage sans délai, les actions de l’utilisateur, continuer à exécuter d’éventuelles tâches de
Généricité fond, jouer des animations, etc.
Gestion des • De manière générale, c’est utile dans tout programme qui doit réagir immédiatement à
erreurs et
exceptions des événements de causes et origines variées et indépendantes.
Interfaces
graphiques • Possible 359 pour de nombreux algorithmes décomposables en étapes
Concurrence
Introduction
indépendantes.
Utile de programmer ces algorithmes de façon concurrente car on peut profiter du
Concurrence et
parallélisme
Les abstractions
Threads en Java
Dompter le JMM
parallélisme pour les accélérer (cf. page suivante).
APIs de haut niveau

358. Et pas seulement en programmation, mais c’est le sujet qui nous intéresse !
359. Et discutablement naturelle aussi.
Compléments
en POO Parallélisme
Aldric Degorre

Deux travaux 360 s’exécutent en parallèle, s’ils exécutent en même temps.


Introduction

Style

Objets et
classes
• Simultanéité au niveau le plus bas : si 2 travaux s’exécutent en parallèle, à un
Types et instant t, s’exécutent en meme temps une instruction de l’un et de l’autre.
polymorphisme

Héritage
• → exécution sur 2 lieux physiques différents (e.g. 2 cœurs, 2 circuits, ...).
Généricité • Degré de parallélisme 361 = nombre de travaux simultanément exécutables.
Gestion des
erreurs et
exceptions Pour des raisons économiques et technologiques, les microprocesseurs modernes
Interfaces (multi-cœur 362 ) ont typiquement un degré de parallélisme ≥ 2.
graphiques

Concurrence C’est une opportunité qu’il faut savoir saisir !


Introduction
Concurrence et
parallélisme
Les abstractions
Threads en Java 360. Pour ne pas dire “processus”, qui a un sens un peu trop précis en informatique.
361. D’une plateforme d’exécution.
Dompter le JMM
APIs de haut niveau

362. Sur les CPU de moyenne et haut de gamme, le degré de parallélisme est généralement de 2 par cœur,
grâce au SMT (simultaneous multithreading), appelé hyperthreading chez Intel
Compléments
en POO Concurrence
Aldric Degorre Enjeu

Introduction

Style

Objets et
classes Ainsi, l’enjeu de la programmation concurrente est double :
Types et
polymorphisme
• Nécessité : pouvoir programmer des fonctionnalités intrinsèquement concurrentes
Héritage
(serveur web, IG, etc.).
Généricité

Gestion des • Opportunisme : tirer partie de toute la puissance de calcul du matériel


erreurs et
exceptions contemporain.
Interfaces En effet : des travaux indépendants (concurrents) peuvent naturellement être confiés à des
graphiques
unités d’exécution distinctes (parallèles).
Concurrence
Introduction
Concurrence et
parallélisme
Malheureusement, la programmation concurrente est en réalité un art difficile...
Les abstractions
Threads en Java
Dompter le JMM
APIs de haut niveau
Compléments
en POO Concurrence
Aldric Degorre Les difficultés

Introduction

Exécuter deux travaux réellement concurrents en parallèle est facile 363 , mais la réalité
Style

Objets et
classes est souvent plus compliquées :
Types et
polymorphisme
• Si concurrence > parallélisme, partage du temps des unités d’exécution (→
Héritage

Généricité
ordonnanceur nécessaire).
Gestion des
erreurs et
• Concurrence de 2 sous-programmes jamais parfaite 364 car nécessité de se
exceptions
transmettre/partager des résultats et de se synchroniser. 365
Interfaces
graphiques

Concurrence
→ Différentes abstractions pour aider à programmer de façon correcte et, si possible,
Introduction
Concurrence et
intuitive, tout en prenant en compte ces réalités de diverses façons.
parallélisme
Les abstractions
Threads en Java

363. On en affecte un à chaque cœur, pourvu qu’il y ait 2 cœurs disponibles, et on n’en parle plus !
Dompter le JMM
APIs de haut niveau

364. Sinon ce ne seraient des sous-programmes mais des programmes indépendants à part entière !
365. En fait, ces deux aspects sont indissociables.
Compléments
en POO Questions de synchronisation
Aldric Degorre Transmettre des résultats d’une tâche à l’autre

Introduction
Plusieurs techniques de transmission de résultats :
Style

Objets et • variables partagées : variables accessibles par plusieurs tâches concurrentes.


classes

Types et Données partagées de façon transparente, sans synchronisation a priori, mais le


polymorphisme
langage permet d’insérer des primitives de synchronisation explicite 366 .
Héritage

Généricité • passage de message : pas de partage entre les tâches, mais “envois” 367 de
Gestion des données.
erreurs et
exceptions La synchronisation s’articule autour de la réception du message : la tâche qui a
Interfaces
graphiques
besoin de recevoir la donnée est bloquée tant qu’elle n’a rien reçu.
Concurrence
Introduction La réalité physique est plus proche du modèle des variables partagées 368 , mais le
Concurrence et
parallélisme passage de message est un paradigme plus sûr 369 .
Les abstractions
Threads en Java 366. En Java : start(), join(), volatile, synchronized et wait()/notify().
367. Sous-entendu : l’envoyeur ne peut plus accéder à ce qui a été envoyé.
Dompter le JMM
APIs de haut niveau

368. Mémoire centrale lisible par plusieurs CPU.


369. Pour lequel la sûreté d’un programme est plus facile à prouver.
Compléments
en POO Questions de synchronisation
Aldric Degorre Se passer des résultats

Introduction

Style
Mais on peut passer d’un paradigme à l’autre avec un peu d’enrobage :
Objets et
classes p u b l i c f i n a l c l a s s MailBox <T> { / / c l a s s e d ’ enrobage
p r i v a t e T c o n t e n t ; / / mé m o i r e p a r t a g é e , c a c h é e
Types et p u b l i c s y n c h r o n i z e d v o i d sendMessage ( T message ) {
polymorphisme while ( content != n u l l ) wait ( ) ;
Héritage c o n t e n t = message ;
notify () ;
Généricité }
public synchronized T receiveMessage ( ) {
Gestion des w h i l e ( c o n t e n t == n u l l ) w a i t ( ) ;
erreurs et T ret = content ; content = null ;
exceptions notify () ;
return ret ;
Interfaces }
graphiques }
Concurrence
p u b l i c f i n a l c l a s s MyThread e x t e n d s T h r e a d { / / c l a s s e s a n s é t a t p a r t a g é
Introduction
p r i v a t e f i n a l M a i l B o x < S t r i n g > mb = new M a i l B o x ( ) ;
Concurrence et
parallélisme @ O v e r r i d e p u b l i c v o i d r u n ( ) { w h i l e ( t r u e ) System . o u t . p r i n t l n ( mb . r e c e i v e M e s s a g e ( ) ) ; }
Les abstractions p u b l i c f i n a l v o i d sendMessage ( S t r i n g msg ) { mb . sendMessage ( msg ) ; }
Threads en Java }
Dompter le JMM
APIs de haut niveau
Compléments
en POO Questions de synchronisation
Aldric Degorre Recevoir des messages

Introduction

Style
• fonctions bloquantes : la tâche réceptrice appelle une fonction fournie par la
Objets et bibliothèque, qui la bloque jusqu’à ce que la valeur attendue soit disponible.
classes
F o r k J o i n T a s k < R e s u l t > t a s k = F o r k J o i n T a s k . a d a p t ( ( ) −> {
Types et . . . / / t â che 1
polymorphisme return result ;
}) . fork () ;
Héritage
...
Généricité // plus loin
Result x = task . j o i n ( ) ; // appel à fonction bloquante j o i n ( )
Gestion des . . . / / t â c h e 2 : f a i t qqc a v e c l e r é s u l t a t x de t â c h e 1
erreurs et
exceptions

Interfaces • fonctions de rappel (callbacks) : une fonction, fournie par le programmeur, est
graphiques

Concurrence
appelée dès qu’un résultat est disponible, avec celui-ci en paramètre.
Introduction
Concurrence et
C o m p l e t a b l e F u t u r e . s u p p l y A s y n c ( ( ) −> {
parallélisme . . . / / t â che 1
Les abstractions return result ;
Threads en Java } ) . t h e n A p p l y ( ( x ) −> { / / c o r p s de l a f o n c t i o n de r a p p e l
Dompter le JMM . . . / / t â c h e 2 : f a i t qqc a v e c l e r é s u l t a t x de t â c h e 1
APIs de haut niveau }) ;
Compléments
en POO Questions de synchronisation
Aldric Degorre Envoyer des messages

Introduction

Style

Objets et Envoyer le résultat x d’un calcul, ça peut être simplement :


classes

Types et • retourner x à la fin d’une fonction (tâche productrice), c’est le cas dans les 2
polymorphisme
exemples précédents (“return result”).
Héritage

Généricité • passer x en paramètre d’un appel de méthode. Par exemple, on peut soumettre la
Gestion des valeur à une file d’attente synchronisée :
erreurs et
exceptions
... // calcule x
Interfaces queue.offer(x);
graphiques
... // fais autre chose (avec interdiction de toucher à x !)
Concurrence
Introduction
Concurrence et
parallélisme
Dans ce dernier cas, la tâche consommatrice reçoit le message en appelant
Les abstractions
Threads en Java
queue.take() (fonction bloquante).
Dompter le JMM
APIs de haut niveau
Compléments
en POO Questions d’ordonnancement
Aldric Degorre Multi-tâche préemptif vs. coopératif

Introduction

Style

Objets et
classes

Types et L’ordonnanceur peut mettre en œuvre :


polymorphisme

Héritage • un fonctionnement multi-tâches préemptif : l’ordonnanceur choisit quand mettre en


Généricité pause une tâche pour reprendre l’exécution d’une autre. Cela peut arriver (presque)
Gestion des
erreurs et
à tout moment.
exceptions
• ou bien un fonctionnement multi-tâches coopératif : chaque tâche signale à
Interfaces
graphiques l’ordonnanceur quand elle peut être mise en attente (par exemple en faisant un
Concurrence
Introduction
appel bloquant).
Concurrence et
parallélisme
Les abstractions
Threads en Java
Dompter le JMM
APIs de haut niveau
Compléments
en POO Notion de thread
Aldric Degorre

Introduction Definition (Thread ou fil d’exécution)


Style

Objets et Abstraction concurrente consistant en une séquence d’instructions dont l’exécution


classes
simule une exécution séquentielle 370 et parallèle à celle des autres threads.
Types et
polymorphisme

Héritage

Généricité
• Un nombre quelconque de threads s’exécute sur une plateforme de parallélisme
Gestion des quelconque 371 . Un ordonnanceur veille à partager les ressources de la plateforme.
erreurs et
exceptions • Ainsi, n threads en exécution simultanée simulent un parallélisme de degré n
Interfaces
graphiques • Un processus 372 (= application en exécution) peut utiliser plusieurs threads qui ont
Concurrence
Introduction
accès aux mêmes données (mémoire partagée).
Concurrence et
parallélisme
Les abstractions
Threads en Java 370. Ce qui permet de le programmer avec les principes habitules de programmation impérative : séquences
d’instructions, boucles, branchements, pile d’appels de fonctions, ...
Dompter le JMM
APIs de haut niveau

371. Même inférieur au nombre de threads


372. Cette fois-ci au sens où on l’entend en informatique.
Compléments
en POO Notion de thread
Aldric Degorre Exemple simple (1)

Introduction

Style Exemple de deux threads, l’un qui compte jusqu’à 10 alors que l’autre récite l’alphabet :
Objets et
classes
class ReciteNombres extends Thread {
Types et
polymorphisme @Override
public void run() {
Héritage
for (int i = 0; i < 10; i++)
Généricité System.out.print(i + "␣");
Gestion des }
erreurs et }
exceptions

Interfaces class ReciteAlphabet extends Thread {


graphiques
@Override
Concurrence public void run() {
Introduction
for (int i = 0; i < 26; i++)
Concurrence et
parallélisme System.out.print((char)('a'+i) + "␣");
Les abstractions
}
Threads en Java
Dompter le JMM }
APIs de haut niveau

Exemple
Compléments
en POO Notion de Thread
Aldric Degorre Exemple simple (2)

Introduction Alors
Style
public class Exemple {
Objets et
classes
public static void main(String[] args) {
new ReciteNombres().start(); new ReciteAlphabet().start();
Types et
}
polymorphisme
}
Héritage

Généricité

Gestion des
peut afficher
erreurs et
exceptions 0 1 2 3 4 5 6 7 8 9 a b c d e f g h i j k l m n o p q r s t u v w x y z
Interfaces
graphiques
mais également
Concurrence
Introduction
0 1 2 3 a b c d 4 5 e 6 f 7 g 8 h 9 i j k l m n o p q r s t u v w x y z
Concurrence et
parallélisme
Les abstractions
Threads en Java
Dompter le JMM
ou encore
APIs de haut niveau
0 1 2 3 4 a 5 b 6 c 7 d 8 e 9 f g h i j k l m n o p q r s t u v w x y z
Exemple
Compléments
en POO Notion de thread
Aldric Degorre Un concept décliné à plusieurs niveaux (1)

Introduction

Style • Dans le matériel : p. ex., dans les processeurs Intel Core i7, un même cœur exécute
Objets et
classes
2 threads simultanés pour pouvoir utiliser optimalement tous les composants du
Types et
pipeline (SMT/hyperthreading).
polymorphisme
Ces 2 threads sont présentés à l’OS 373 comme des processeurs séquentiels à part
Héritage
entière (ainsi un i7 à 4 cœurs, apparaît, pour le système, comme 8 processeurs).
Généricité

Gestion des • Dans les OS multi-tâches : afin que plusieurs logiciels puissent s’exécuter en
erreurs et
exceptions même temps, un OS est capable d’instancier un “grand” 374 nombre de threads (on
Interfaces parle de “threads système”).
graphiques

Concurrence
L’OS contient un ordonnanceur affectant tour à tour les threads aux différents
Introduction
Concurrence et
processeurs 375 en gérant les changements de contexte. 376
parallélisme
Les abstractions
Threads en Java 373. Operating System/système d’exploitation
374. On parle typiquement de milliers, pas de millions. La limite pratique est la mémoire disponible.
Dompter le JMM
APIs de haut niveau

375. réels ou simulés, cf. hyperthreading


376. Contexte = pointeur de pile, pointeur ordinal, différents registres...
Compléments
en POO Notion de thread
Aldric Degorre Un concept décliné à plusieurs niveaux (2)

Introduction

Style • Dans le runtime des langages de programmation : des langages de programmation


Objets et
classes
(Erlang, Go, Haskell, Lua, ..., mais pas actuellement Java), contiennent une notion de
Types et thread “léger” (différents noms : green thread, fibre, coroutine, goroutine, ...),
polymorphisme
s’exécutant par dessus un ou des threads système.
Héritage

Généricité
Langage/runtime Abstractions (fibres, coroutines, acteurs, futurs, évènements, ...)
Gestion des
erreurs et thread thread threadthread thread thread thread 377 ...
exceptions

Interfaces
OS (noyau) Ordonnanceur
graphiques
Proc. logique Proc. logique Proc. logique Proc. logique
Concurrence
Introduction SMT SMT
Matériel Cœur Cœur
Concurrence et
parallélisme

CPU 378
Les abstractions
Threads en Java
Dompter le JMM
APIs de haut niveau

377. Sous-entendu : “thread système” (“thread” sans précision = “thread système”).


378. Possible aussi : plusieurs CPUs (plusieurs cœurs par CPU, plusieurs processeurs logiques par cœur...)
Compléments
en POO À propos des threads système
Aldric Degorre Avantages et inconvénients

Introduction

Style • Ce sont des threads.


Objets et
classes
Avantage : se programment séquentiellement (respectent les habitudes).
Types et Inconvénient : la synchronisation doit être explicitée par le programmeur. 379
polymorphisme

Héritage
• Multi-tâche préemptif : l’ordonnanceur peut suspendre un thread (au profit d’un
Généricité autre), à tout moment
Gestion des
erreurs et
Avantage : pas besoin de signaler quand le programme doit “laisser la main”.
exceptions
Inconvénient : changements de contexte fréquents et coûteux.
Interfaces
graphiques • Implémentation dans le noyau :
Concurrence
Introduction Avantage : compatible avec tous les exécutables de l’OS (pas seulement JVM)
Inconvénient : fonctionnalités rudimentaires. P. ex., chaque thread a une pile de
Concurrence et
parallélisme
Les abstractions
Threads en Java
Dompter le JMM
taille fixe (1024 ko pour les threads de la JVM 64bits) → peu économique !
APIs de haut niveau

379. On va voir dans la suite comment. Pour l’instant, retenez qu’il n’y a aucune synchronisation, donc aucun
partage de données sûr entre threads si on n’ajoute pas “quelque chose”.
Compléments
en POO Des APIs de haut niveau pour ne pas manipuler les threads
Aldric Degorre

Introduction

Style

Objets et
classes → les langages de programmation proposent des mécanismes, utilisant les threads
Types et système, pour pallier leurs inconvénients tout en essayant 380 de garder leur avantages.
polymorphisme

Héritage Ces APIs ont au moins l’un des deux objectifs suivants :
Généricité
• limiter le nombre de threads système utilisés, afin de diminuer l’empreinte mémoire
Gestion des
erreurs et
exceptions
et la fréquence des changements de contexte
Interfaces • forcer des procédés sûrs pour le partage de données ; ou à défaut, faciliter les
graphiques

Concurrence
bonnes pratiques de synchronisation.
Introduction
Concurrence et
parallélisme
Les abstractions
Threads en Java
Dompter le JMM
APIs de haut niveau

380. avec plus ou moins de succès


Compléments
en POO La situation de Java
Aldric Degorre

Introduction Java
Style

Objets et
• utilise directement les threads système, via la classe Thread.
classes
• a historiquement (Java 1.1) utilisé des green threads 381 , abandonnés pour des
Types et
polymorphisme raisons de performance 382 .
Héritage
• pourrait néanmoins, dans le futur, supporter les fibres 383 via le projet Loom.
Généricité

Gestion des • dispose actuellement d’un grand nombre d’APIs facilitant où rendant plus sûre
erreurs et
exceptions l’utilisation des threads : les boucles d’évènements Swing et JavaFX,
Interfaces
graphiques
ThreadPoolExecutor, ForkJoinPool/ForkJoinTask,
Concurrence
CompletableFuture, Stream...
Introduction
Concurrence et
parallélisme
Les abstractions
381. Une sorte de threads légers.
Threads en Java 382. Ils étaient ordonnancés sur un seul thread système, empêchant d’utiliser plusieurs processeurs.
383. Autre type de threads légers. Cette fois-ci, le travail peut être distribué sur plusieurs threads système.
Dompter le JMM
APIs de haut niveau

Des implémentations de fibres pour Java existent déjà : bibliothèques Quasar et Kilim. Mais pour
fonctionner, celles-ci doivent modifier le code-octet généré par javac.
Compléments
en POO Threads en Java
Aldric Degorre

Introduction • 1 thread est associé à 1 pile d’appel de méthodes


Style Thread principal en Java = pile des méthodes appelées depuis l’appel initial à
Objets et
classes
main()
Types et → vous utilisez déjà des threads !
polymorphisme

Héritage
• Interfaces graphiques (Swing, JavaFX, ...) : un thread 384 (̸= main) est dédié aux
Généricité
évènements de l’IG :
Gestion des • Programmation événementielle → méthodes gestionnaires d’événement
erreurs et
exceptions
• Événements → mis en file d’attente quand ils surviennent.
Interfaces
• Quand le thread des évènements est libre, le gestionnaire correspondant au premier
graphiques événement de la file est appelé et exécuté sur ce thread.
Concurrence
Introduction Intérêt : 385 pas besoin de prévoir des interruptions régulières dans le thread main
pour vérifier et traiter les événements en attente (l’IG resterait figée entre deux)
Threads en Java
Introduction
La classe Thread
Synchronisation
Dompter le JMM
APIs de haut niveau
384. Pour Swing, d’EDT : Event Dispatching Thread
385. Et l’intérêt de n’avoir qu’un seul thread pour cela : la sûreté du fonctionnement de l’IG. Pas
d’entrelacements entre 2 évènements, pas d’accès compétition.
Compléments
en POO Threads en Java
Aldric Degorre Concrètement

Introduction

Style

Objets et • Tous les threads ont accès au même tas (mêmes objets) et à la même zone
classes
statique (mêmes classes)... mais pas à la même pile !
Types et
polymorphisme
• Les threads communiquent ainsi grâce aux variables partagées.
Héritage

Généricité • Une même méthode peut être appelée depuis n’importe quel thread (pas de
Gestion des séparation syntaxique du code associé aux différents threads).
erreurs et
exceptions
• Chaque thread correspond à une pile d’appels de méthode. Bas de la pile : le frame
Interfaces
graphiques de la méthode main pour le thread main ; celui de run (classe Thread) pour les
Concurrence autres.
Introduction
Threads en Java
Introduction
• Pour démarrer un thread : unObjetThread.start();.
La classe Thread
Synchronisation → aussitôt, appel de unObjetThread.run() dans le thread associé à cet objet.
Dompter le JMM
APIs de haut niveau
Compléments
en POO Créer un thread en Java
Aldric Degorre

Introduction Deux techniques classiques pour créer un thread :


Style

Objets et
• Définir et instancier une classe héritant de la classe Thread :
classes
public class HelloThread extends Thread {
Types et
polymorphisme @Override public void run() { System.out.println("Hello␣from␣a␣thread!"); }
Héritage
public static void main(String args[]) { new HelloThread().start(); }
Généricité }
Gestion des
erreurs et
exceptions • Implémenter Runnable et appeler le constructeur Thread(Runnable target) :
Interfaces
graphiques public class HelloRunnable implements Runnable {
Concurrence @Override public void run() { System.out.println("Hello␣from␣a␣thread!"); }
Introduction
Threads en Java
Introduction
public static void main(String args[]) { new Thread(new
La classe Thread HelloRunnable()).start(); }
Synchronisation }
Dompter le JMM
APIs de haut niveau

Détails
Compléments
en POO Créer un thread en Java
Aldric Degorre Remarques

Introduction

Style
• La classe Thread implémente Runnable et y ajoute, en outre, la méthode start.
Objets et
classes
• Runnable n’a pas de méthode pour démarrer ou piloter un thread, mais on peut en
Types et
polymorphisme exécuter une instance dans un thread donné, en la passant à une instance de
Héritage Thread.
Généricité

Gestion des
• L’approche consistant à définir des tâches en implémentant Runnable plutôt qu’en
erreurs et
exceptions
étendant Thread laisse la possibilité d’hériter d’une classe supplémentaire :
Interfaces public class MaClasse extends JFrame implements Runnable {
graphiques
public void run() {
Concurrence ...
Introduction
}
Threads en Java
Introduction ...
La classe Thread }
Synchronisation
Dompter le JMM
APIs de haut niveau

Détails
Compléments
en POO La classe Thread
Aldric Degorre Méthodes importantes

Introduction
• String getName() : récupérer le nom d’un thread.
Style
• void join() : attendre la fin de ce thread (voir synchronisation).
Objets et
classes • void run() : la méthode qui lance tout le travail de ce Thread. C’est la méthode
Types et
polymorphisme qu’il faudra redéfinir à chaque fois que Thread sera étendue ! .
Héritage • static void sleep(long millis) : met le thread courant (i.e. en cours
Généricité d’exécution) en pause pendant tant de ms. (NB : c’est une méthode static. Le
Gestion des
erreurs et
thread mis en pause est celui qui appelle la méthode. Il n’y a pas de this !).
exceptions
• void start() : démarre le thread (conséquence : run() est exécutée dans le
Interfaces
graphiques nouveau thread : celui décrit par l’objet, pas celui de l’appelant !)..
Concurrence
Introduction
• void interrupt() : interrompt le thread (déclenche InterruptedException
Threads en Java
Introduction
si le thread était en attente sur wait(), join(), sleep(),..)
La classe Thread
Synchronisation
• static boolean interrupted() : teste si un autre thread a demandé
Dompter le JMM
APIs de haut niveau
l’interruption du thread courant.
• Thread.State getState() : retourne l’état du thread.
Compléments
en POO La classe Thread
Aldric Degorre États d’un thread
Introduction

Style

Objets et Une instance de thread est toujours dans un des états suivants :
classes

Types et
polymorphisme
• NEW : juste créé, pas encore démarré.
Héritage • RUNNABLE : en cours d’exécution.
Généricité

Gestion des
• BLOCKED : en attente de moniteur (voir la suite).
erreurs et
exceptions • WAITING : en attente d’une condition d’un autre thread (voir notify()/wait()).
Interfaces
graphiques • TIME_WAITING : idem pour attente avec temps limite.
Concurrence
Introduction • TERMINATED : exécution terminée.
Threads en Java
Introduction
La classe Thread Mais attendons la suite pour en dire plus sur ces états...
Synchronisation
Dompter le JMM
APIs de haut niveau

Détails
Compléments
en POO La classe Thread
Aldric Degorre Interruptions (1)

Introduction

Style • On peut essayer d’interrompre un thread t depuis un autre : il suffit d’y appeler
Objets et
classes
t.interrupt().
Types et • Si t est en train d’exécuter une méthode interruptible 386 , celle-ci quitte sur
polymorphisme

Héritage
InterruptedException.
Généricité • L’exception est propagée le long de la pile d’appel...
Gestion des
erreurs et • ... jusqu’à arriver dans une méthode 387 qui la rattrape (catch). Celle-ci doit
exceptions

Interfaces
normalement faire en sorte de se quitter au plus vite.
graphiques
• Le résultat attendu, mais non garanti 388 , est la terminaison du thread 389 .
Concurrence
Introduction
Threads en Java
Introduction 386. C’est le cas de toutes les méthodes bloquantes de l’API Thread : wait(), sleep(),join()...
La classe Thread
Synchronisation 387. Habituellement : run.
Dompter le JMM
APIs de haut niveau
388. Si les méthodes exécutées sur t n’ont pas prévu d’être interrompues, rien ne se passe.
389. Sauf exécution dans un thread pool, auquel cas on s’efforce de juste terminer la tâche en cours pour
libérer le thread.
Compléments
en POO La classe Thread
Aldric Degorre Interruptions (2)

Introduction

Style Pour écrire une méthode interruptible f :


Objets et
classes • déclarer throws InterruptedException dans sa signature ;
Types et
polymorphisme • tout traitement long de f doit pouvoir être interrompu ;
Héritage

Généricité
• ainsi, dans toute boucle de f (sans appel bloquant), il faut vérifier si une
Gestion des interruption a été demandée en appelant Thread.interrupted()
erreurs et
exceptions • si c’est le cas, il faut d’agir en conséquence : libérer les ressources et lever
Interfaces
graphiques
InterruptedException
Concurrence • si une méthode g appelée depuis f quitte sur InterruptedException il faut
Introduction
Threads en Java
Introduction
s’assurer que f libère bien ses resources (appel de g dans bloc try/finally ou
La classe Thread
Synchronisation
bien try-with-resource). Ne pas rattraper InterruptedException ici (pas de
Dompter le JMM
APIs de haut niveau
catch).
Compléments
en POO La classe Thread
Aldric Degorre Interruptions (3)

Introduction Exemples de méthodes interruptibles :


Style
// avec while et acquisition/libération de resource (bloc "try-with-resource")
Objets et Data f(Data x) throws InterruptedException {
classes
try (Scanner s = new Scanner(System.in)) {
Types et while(test(x)) {
polymorphisme
x = transform(x, s.next());
Héritage if (Thread.interrupted()) throw new InterruptedException(); // <-- ici !
Généricité }
return x;
Gestion des
erreurs et } // s.close() appelée implicitement à la sortie du bloc (par throw ou par return)
exceptions }
Interfaces
graphiques // exemple sans boucle, mais avec appel bloquant
Concurrence void sleep5s() throws InterruptedException {
Introduction System.out.println("Acquisition␣potentielle␣de␣ressource");
Threads en Java
try {
Introduction
La classe Thread Thread.sleep(5000); // on attend 5s
Synchronisation } finally { System.out.println("Libération␣de␣la␣même␣ressource"); }
Dompter le JMM
APIs de haut niveau
// Pas de "catch". Si sleep() envoie InterruptedException, elle est propagée.
}
Exemple
Compléments
en POO Problèmes liés au multithreading
Aldric Degorre

Deux principaux problèmes :


Introduction

Style

Objets et
1 Les entrelacements non maîtrisés : les instructions de 2 threads s’entrelacent 390
classes
et accèdent (lecture et écriture) aux mêmes données dans un ordre imprévisible.
Types et
polymorphisme Ce phénomène est “naturel” (l’ordonnanceur est “libre” de faire avancer un thread,
Héritage puis l’autre au moment où il veut) ; il peut parfois être gênant, parfois non.
Généricité
2 Les incohérences dues aux optimisations matérielles 391 : la JVM 392 laisse une
Gestion des
erreurs et marge d’interprétation assez large au matériel pour qu’il puisse exécuter le
exceptions
programme efficacement. Principales conséquences :
Interfaces
graphiques • ordre des instructions donné dans le code source pas forcément respecté
Concurrence • modifications de variables partagées pas forcément vues par les autres threads.
Introduction
Threads en Java

Pour l’instant, concentrons nous sur le problème 1.


Introduction
La classe Thread
Synchronisation
Dompter le JMM
390. interleave
APIs de haut niveau
391. en particulier dans le microprocesseur
Résumé 392. La JVM s’appuie sur le JMM : Java Memory Model, un modèle d’exécution relativement laxe.
Compléments
en POO Entrelacements de threads
Aldric Degorre Exemple (1)

Introduction
Qu’est-ce qui est affiché quand on exécute le programme suivant ?
Style

Objets et public class ThreadInterferences extends Thread {


classes static int x = 0;
Types et
polymorphisme public ThreadInterferences(String name){ super(name); }
Héritage
@Override
Généricité
public void run() {
Gestion des while(x++ < 10) System.out.println("x␣incrémenté␣par␣" + getName() + ",␣sa␣
erreurs et
exceptions nouvelle␣valeur␣est␣" + x + ".");
}
Interfaces
graphiques
public static void main(String[] args){
Concurrence
new ThreadInterferences("t1").start();
Introduction
Threads en Java new ThreadInterferences("t2").start();
Introduction }
La classe Thread
Synchronisation
}
Dompter le JMM
APIs de haut niveau

Exemple On s’attend à voir tous les entier de 1 à 10 s’afficher dans l’ordre.


Compléments
en POO Entrelacements de threads
Aldric Degorre Exemple (2)

Introduction

Style

Objets et Exécution possible :


classes
x incrémenté par t2, sa nouvelle valeur est 2.
Types et
polymorphisme x incrémenté par t1, sa nouvelle valeur est 2.
x incrémenté par t2, sa nouvelle valeur est 3.
Héritage
x incrémenté par t1, sa nouvelle valeur est 4.
Généricité x incrémenté par t2, sa nouvelle valeur est 5.
Gestion des x incrémenté par t1, sa nouvelle valeur est 6.
erreurs et x incrémenté par t2, sa nouvelle valeur est 7.
exceptions
x incrémenté par t2, sa nouvelle valeur est 9.
Interfaces x incrémenté par t2, sa nouvelle valeur est 10.
graphiques
x incrémenté par t1, sa nouvelle valeur est 8.
Concurrence
Introduction
Threads en Java
Introduction Contrairement à ce qu’on pourrait attendre : les nombres ne sont pas dans l’ordre,
La classe Thread
Synchronisation certains se répètent, d’autres n’apparaissent pas.
Dompter le JMM
APIs de haut niveau

Exemple
Compléments
en POO Accès atomique
Aldric Degorre

Introduction
Avec quelle granularité les entrelacements se font-ils ? Peut-on s’arrêter au milieu d’une
Style
affectation, faire autre chose sur la même variable, puis finir ? → notion clé : atomicité
Objets et
classes • Atomique = non séparable (étym.), non entrelaçable (ici).
Types et
polymorphisme Aucune autre instruction, accédant aux mêmes données, ne peut être exécutée
Héritage pendant celle des instructions d’une opération atomique.
Généricité • Quelques exemples d’opérations atomiques :
Gestion des
erreurs et • lecture ou affectation de valeur 32 bits (boolean, char, byte, short, int, float) ;
exceptions
• lecture ou affectation de référence (juste la référence, pas le contenu de l’objet) ;
Interfaces
graphiques
• lecture ou affectation d’attribut volatile 393 ;
Concurrence
• exécution d’un bloc synchronized 394
Introduction
Threads en Java
Introduction
• Exemple d’opération non atomique : x++ (peut se décomposer ainsi : copie x en
La classe Thread
Synchronisation
pile, empile 1, additionne, copie le sommet de pile dans x).
Dompter le JMM
APIs de haut niveau
393. Notion abordée plus loin.
394. Idem. Dans ce cas, remplacer “accédant aux mêmes données” par “utilisant le même verrou”.
Compléments
en POO La synchronisation
Aldric Degorre

Synchronisation :
Introduction

Style • consiste, pour un thread, à attendre le “feu vert” d’un autre thread avant de
Objets et
classes continuer son exécution ;
Types et
polymorphisme
• interdit certains entrelacements ;
Héritage • contribue à établir la relation ”arrivé-avant”, limitant les optimisations
Généricité
autorisées 395 .
Gestion des
erreurs et
exceptions
a b
Interfaces
graphiques

Concurrence a1 b1 Ici, a1 arrive avant a2 , b1 avant b2 , a1 avant b2 ,


Introduction
Threads en Java
sync mais pas b1 avant a2 !
Introduction
La classe Thread
Synchronisation
Dompter le JMM
a2 b2
APIs de haut niveau

395. À suivre...
Compléments
en POO Avec la classe Thread : méthode join()
Aldric Degorre

Synchronisation simple : attendre la terminaison d’un thread avec join() 396 :


Introduction

Style public class ThreadJoin extends Thread {


static int x = 0;
Objets et
classes
@Override
Types et
polymorphisme public void run(){ System.out.println(x); }

Héritage
public static void main(String[] args){
Généricité Thread t = new ThreadJoin();
Gestion des t.start();
erreurs et t.join();
exceptions
x++;
Interfaces }
graphiques
}
Concurrence
Introduction
Threads en Java
Introduction
affiche 0 alors que le même code sans l’appel à join() affiche 1.
La classe Thread
Synchronisation
Dompter le JMM
Tout ce qui est exécuté dans le thread t arrive-avant ce qui suit le join() dans le thread
APIs de haut niveau
main (ici, l’incrémentation de x).
396. Existe aussi en version temporisée : on bloque jusqu’au délai donné en paramètre maximum.
Compléments
en POO Synchronisation avec join()
Aldric Degorre

Introduction t1 t2
Style

Objets et
classes

Types et
avant start()
polymorphisme t2.start()
Héritage run()
Généricité

Gestion des
erreurs et
exceptions

Interfaces t2.join()
graphiques

Concurrence
Introduction
Threads en Java
Introduction
La classe Thread
Synchronisation
Dompter le JMM après join()
APIs de haut niveau
Compléments
en POO Le verrou intrinsèque
Aldric Degorre Qu’est-ce, à quoi cela sert-il ?

Introduction

Style
• En Java tout objet contient un verrou intrinsèque (ou moniteur).
Objets et
classes
• À tout moment, le moniteur est soit libre, soit détenu par un (seul) thread donné.
Types et
polymorphisme Ainsi un moniteur met en œuvre le principe d’exclusion mutuelle.
Héritage
• Lors de son exécution, un thread t peut demander à prendre un moniteur.
Généricité

Gestion des
• Si le moniteur est libre, alors il est pris par t, qui continue son exécution.
erreurs et • Si le moniteur est déjà pris, t est alors mis en attente jusqu’à ce que le moniteur se
exceptions

Interfaces
libère pour lui (il peut y avoir une liste d’attente).
graphiques

Concurrence
• Un thread peut à tout moment libérer un moniteur qu’il possède.
Introduction
Threads en Java
Introduction Conséquence : tout ce qui se produit dans un thread avant qu’il libère un moniteur
La classe Thread
Synchronisation arrive-avant ce qui se produit dans le prochain thread qui obtiendra le moniteur, après
Dompter le JMM
APIs de haut niveau l’obtention de celui-ci.
Compléments
en POO Le verrou intrinsèque
Aldric Degorre Utilisation en pratique : le mot-clé synchronized

Introduction

Style
Bloc synchronisé :
Objets et class AutreCompteur{
classes
private int valeur;
Types et private Object verrou = new Object(); // peu importe le type déclaré
polymorphisme public void incr(){
Héritage synchronized(verrou) { // <--- ici !
Généricité
valeur++;
}
Gestion des
erreurs et
}
exceptions }
Interfaces
graphiques

Concurrence
Sémantique : le thread qui exécute ce bloc demande le moniteur de verrou en y entrant
Introduction
Threads en Java
et le libère en en sortant.
Conséquence : pour une instance donnée de AutreCompteur, le bloc n’est exécuté que
Introduction
La classe Thread

par un seul thread en même temps (exclusion mutuelle). Les autres threads qui essayent
Synchronisation
Dompter le JMM

d’y entrer sont suspendus (BLOCKED).


APIs de haut niveau
Compléments
en POO Le verrou intrinsèque
Aldric Degorre Utilisation en pratique : le mot-clé synchronized

Introduction
Méthode synchronisée : cas particulier avec synchronisation de tout le corps de la
Style
méthode sur moniteur de this. → syntaxe plus légère, plus souvent utilisée en pratique.
Objets et
classes

Types et équivalent à...


polymorphisme class Compteur {
private int valeur; class Compteur {
Héritage
// méthode contenant bloc synchronisé private int valeur;
Généricité sur this // méthode synchronisée
Gestion des public void incr(){ public synchronized void incr() {
erreurs et synchronized(this) { valeur++; } valeur++;
exceptions
} }
Interfaces } }
graphiques

Concurrence
Introduction
Threads en Java
Introduction
Note : l’exclusion mutuelle porte sur le moniteur (1 par objet) et non sur le bloc
La classe Thread
Synchronisation
synchronisé (souvent plusieurs par moniteur).
Conséquence : 1 bloc synchronisé n’a qu’une seule exécution simultanée. De plus, aucun
Dompter le JMM
APIs de haut niveau

autre bloc synchronisé sur le même moniteur ne sera exécuté en même temps.
Compléments
en POO Le verrou intrinsèque
Aldric Degorre
Compteur cpt = new Compteur();
new Thread(cpt::incremente).start(); new Thread(cpt::incremente).start();
Introduction

Style

Objets et t1 moniteur de cpt t2


classes

Types et
polymorphisme cpt.incremente();
Héritage
prise du moniteur
Généricité

Gestion des cpt.incremente();


erreurs et cpt.valeur++;
exceptions
attente
Interfaces
graphiques libération du moniteur prise du moniteur
Concurrence
Introduction return; cpt.valeur++;
Threads en Java
Introduction libération du moniteur
La classe Thread
Synchronisation
Dompter le JMM return;
APIs de haut niveau
Compléments
en POO Le verrou intrinsèque
Aldric Degorre Le mécanisme notifyAll()/notify()/wait()

Introduction

Style
• 3 méthodes concernées (classe Object) : notify(), notifyAll() et wait().
Objets et
classes
• Méthodes appelables sur un objet x, seulement dans bloc ou méthode synchronisé
Types et
polymorphisme sur x.
Héritage
• wait() : met le thread en sommeil et libère le moniteur (getState() passe de
Généricité
RUNNABLE à WAITING).
Gestion des
erreurs et
exceptions
Le thread restera dans cet état tant qu’il n’est pas réveillé (par notifyAll() ou
Interfaces notify()). Il sera alors en attente pour récupérer le moniteur
graphiques
(WAITING → BLOCKED).
Concurrence
Introduction
Threads en Java
• notifyAll() : réveille tous les threads en attente sur l’objet. Ceux-ci deviennent
Introduction
La classe Thread
candidats à reprendre le moniteur quand il sera libéré.
Synchronisation
Dompter le JMM • notify() : réveille un thread en attente sur l’objet.
APIs de haut niveau
Compléments
en POO Le verrou intrinsèque
Aldric Degorre Le mécanisme notifyAll()/notify()/wait() – conseil d’utilisation

Introduction

Style

Objets et
classes • On utilise wait() pour attendre une condition cond.
Types et
polymorphisme • Mais plusieurs threads peuvent être en attente. Un autre pourrait être libéré et
Héritage récupérer le moniteur avant, rendant la condition à nouveau fausse.
Généricité
• → aucune garantie que cond soit vraie au retour de wait().
Gestion des
erreurs et
exceptions
Ainsi, il faut tester à nouveau jusqu’à satisfaire la condition :
Interfaces
graphiques synchronized(obj) { // conseil : mettre wait dans un while
Concurrence while(!condition(obj)) obj.wait();
Introduction
... // insérer ici instructions qui avaient besoin de condition()
Threads en Java
Introduction
}
La classe Thread
Synchronisation
Dompter le JMM
APIs de haut niveau

Détails
Compléments
en POO Le verrou intrinsèque
Aldric Degorre Le mécanisme notifyAll()/ notify()/wait() – alternatives

Introduction

Style Variantes acceptables :


Objets et
classes • while(!condition())Thread.sleep(temps);
Types et
polymorphisme
→ utile quand on sait qu’aucun thread ne notifiera quand la condition sera vraie.
Héritage • while(!condition())Thread.onSpinWait(); (Java ≥ 9) : attente active,
Généricité
mais onSpinWait signale à l’ordonnanceur que le thread peut être mis en pause
Gestion des
erreurs et en cas de besoin.
exceptions

Interfaces
→ conseillée quand on s’attend à ce que la condition soit vraie très vite → on évite
graphiques le coût de la mise en attente et du réveil.
Concurrence
Introduction
Threads en Java
Déconseillé 397 : while(!condition(obj)/*rien*/; : attente active “bête”
Introduction
La classe Thread
Synchronisation
→ utile pour les attentes courtes (économie du cycle mise en attente/réveil), mais à
Dompter le JMM
APIs de haut niveau
partir de Java 9, aucune raison de ne pas utiliser onSpinWait dans ce cas.
Détails 397. Sauf pour faire cuire une omelette sur son microprocesseur...
Compléments
en POO Retour sur les états d’un thread
Aldric Degorre
État = une valeur dans Thread.State (rectangles) + ensemble de moniteurs détenus
Introduction

Style t = new libère m sortie de


Objets et Thread() t.start() t.run()
classes
NEW RUNNABLE TERMINATED
Types et
polymorphisme
(si m détenu)
Héritage (si m détenu)
m.wait()
Généricité demande m m.wait(d)
Gestion des
erreurs et
exceptions WAITING TIME_WAITING
Interfaces
graphiques

Concurrence m.notify() prend m m.notify()


(dans autre thread) (dans autre thread)
Introduction
Threads en Java
Introduction
La classe Thread
Synchronisation BLOCKED
Dompter le JMM
APIs de haut niveau
En réalité, racourcis directement vers RUNNABLE plutôt que BLOCKED quand le moniteur est déjà disponible.
Résumé
Compléments
en POO Verrous explicites (1)
Aldric Degorre
• moniteurs = principe d’exclusion mutuelle + mécanisme d’attente/notification ;
Introduction

Style
• mais il existe d’autres principes pour synchroniser des threads par rapport à l’usage
Objets et d’une resource (exemple : lecteurs/rédacteur) ;
classes

Types et
• fonctionnalités supplémentaires possibles : demander à qui appartient le verrou,
polymorphisme qui est en attente, etc. ;
Héritage
• → bibliothèque de verrous divers dans java.util.locks, implémentant
Généricité

Gestion des
l’interface java.util.concurrent.locks.Lock.
erreurs et
exceptions
L’interface java.util.concurrent.locks.Lock :
Interfaces
graphiques public interface Lock {
Concurrence void lock();
Introduction void lockInterruptibly() throws InterruptedException;
Threads en Java
Condition newCondition();
Introduction
La classe Thread boolean tryLock();
Synchronisation boolean tryLock(long time, TimeUnit unit) throws InterruptedException;
Dompter le JMM
APIs de haut niveau
void unlock();
}
Supplément
Compléments
en POO Verrous explicites (2)
Aldric Degorre

Introduction
Comme le verrouillage et le déverrouillage se font par appels explicites aux méthodes
Style
lock et unlock, ces verrous sont appelés verrous explicites.
Objets et
classes
Inconvénient : pas de bloc syntaxique, tel synchronized pour forcer à libérer le verrou
en sortant du bloc 398 . La logique du programme doit assurer que tout appel à lock soit
Types et
polymorphisme

Héritage suivi d’un appel à unlock.


Généricité
Avantages :
Gestion des
erreurs et
exceptions • Nombreuses options de configuration.
Interfaces
graphiques • Flexibilité dans l’ordre d’acquisition et de libération. 399
Concurrence
Introduction
Threads en Java
Introduction 398. Mais on peut programmer un tel bloc à la main à l’aide d’une fonction d’ordre supérieur, et encapsuler
La classe Thread
Synchronisation un tel verrou dans une classe dont l’interface ne permettrait d’acquérir le verrou que via cette FOS.
Dompter le JMM
APIs de haut niveau
399. Concurrent Programming in Java (2.5.1.4) montre un exemple de liste chaînée concurrente où, lors d’un
parcours, il est nécessaire d’acquérir le verrou sur le maillon suivant avant de libérer le maillon courant, mais
Supplément on ne souhaite à aucun moment conserver plus de deux verrous.
Compléments
en POO Dangers de la synchronisation
Aldric Degorre quand elle est utilisée à mauvais escient ou à l’excès

Introduction
Un dernier avertissement : la synchronisation doit rester raisonnable !
Style

Objets et En général, plus il y a de synchronisation, moins il y a de parallélisme... et plus le


classes
programme est ralenti. Pire, il peut bloquer.
Types et
polymorphisme
Pathologies typiques :
Héritage

Généricité • dead-lock : 2 threads attendent chacun une ressources que seul l’autre serait à
Gestion des
erreurs et
même de libérer (en fait 2 ou plus : dès lors que la dépendance est cyclique).
exceptions
• famine (starvation) : une ressource est réservée trop souvent/trop longtemps
Interfaces
graphiques toujours par la même tâche, empêchant les autres de progresser.
Concurrence
Introduction • live-lock : boucle infinie causée par plusieurs threads se faisant réagir
mutuellement, sans pour autant faire avancer le programme.
Threads en Java
Introduction
La classe Thread
Synchronisation (S’imaginer deux individus essayant de se croiser dans un couloir, entamant simultanément
une manœuvre d’évitement du même côté, mettant les deux personnes a nouveau l’une face
Dompter le JMM
APIs de haut niveau

à l’autre, provoquant une nouvelle manœuvre d’évitement, et ainsi de suite...)


Résumé
Compléments
en POO Dangers de la synchronisation
Aldric Degorre Exemple de dead lock

Introduction class SynchronizedObject {


p u b l i c s y n c h r o n i z e d v o i d use ( ) { }
Style
public synchronized void useWith ( SynchronizedObject other ) {
Objets et f o r ( i n t i = 0 ; i < 1 0 0 0 ; i + + ) ; / / on s i m u l e un l o n g t r a v a i l
classes System . o u t . p r i n t l n ( T h r e a d . c u r r e n t T h r e a d ( ) + ” ␣ c l a i m s ␣ m o n i t o r ␣on␣ ” + t h i s ) ;
o t h e r . use ( ) ; / / ç a , ç a s e n t m a u v a i s . . .
Types et
}
polymorphisme
}
Héritage
p u b l i c c l a s s De adLo c k e x t e n d s T h r e a d {
Généricité p r i v a t e f i n a l S y n c h r o n i z e d O b j e c t obj1 , obj2 ;

Gestion des p r i v a t e De adLoc k ( S y n c h r o n i z e d O b j e c t o b j 1 , S y n c h r o n i z e d O b j e c t o b j 2 ) {


erreurs et th i s . obj1 = obj1 ; th i s . obj2 = obj2 ;
exceptions }
Interfaces
@Override public void run ( ) {
graphiques
obj1 . useWith ( obj2 ) ;
Concurrence System . o u t . p r i n t l n ( T h r e a d . c u r r e n t T h r e a d ( ) + ” ␣ i s ␣ done . ” ) ;
Introduction
}
Threads en Java
Introduction
p u b l i c s t a t i c v o i d main ( S t r i n g a r g s [ ] ) {
La classe Thread S y n c h r o n i z e d O b j e c t o1 = new S y n c h r o n i z e d O b j e c t ( ) , o2 = new S y n c h r o n i z e d O b j e c t ( ) ;
Synchronisation / / dea d l o c k , s a u f s i l e 1 e r t h r e a d a r r i v e à t e r m i n e r a v a n t que l e 2 e ne commence
Dompter le JMM new De adLoc k ( o1 , o2 ) . s t a r t ( ) ; new DeadLock ( o2 , o1 ) . s t a r t ( ) ;
APIs de haut niveau }
}
Exemple
Compléments
en POO Éviter les dead-locks
Aldric Degorre À l’aide d’une utilisation raisonnée des verrous

Introduction

Style

Objets et
• Principe pour éviter les dead-locks : toujours acquérir les verrous dans le même
classes
ordre 400 et les libérer dans l’ordre inverse 401 (ordre LIFO, donc).
Types et
polymorphisme • En effet : dans l’exemple précédent, une exécution de run veut acquérir o1 puis o2,
Héritage
alors que l’autre exécution veut faire dans l’autre sens.
Généricité

Gestion des
• → quand on écrit un programme concurrent à l’aide de verrous explicites, il faut
erreurs et
exceptions
documenter un ordre unique pour prendre les verrous.
Interfaces
graphiques L’autre voie est de se reposer sur des abstractions de plus haut niveau, sur lesquelles il
Concurrence
Introduction
est plus aisé de raisonner (cf. la suite).
Threads en Java
Introduction
La classe Thread
Synchronisation 400. Pas évident en pratique : verrous créés dynamiquement, difficile de savoir quels verrous existeront à
Dompter le JMM
APIs de haut niveau
l’exécution. On peut aussi ne pas savoir quels verrous une méthode donnée d’une classe tierce utilise.
401. Pour les verrous intrinsèques, ordre inverse imposé par l’imbrication des blocs synchronized. Mais
rien de tel pour les verrous explicites. La preuve de l’absence de dead-lock doit alors se faire au cas par cas.
Compléments
en POO Paradigme des threads
Aldric Degorre Est-ce la réalité ?

Introduction

Style
• Rappel : thread = abstraction
Objets et
classes • simulant la séquentialité dans le thread ;
Types et • permettant une communication instantanée inter-thread via une mémoire partagée ;
polymorphisme
• (et simulant le parallélisme parfait 402 entre threads).
Héritage

Généricité
• Est-ce vraiment la réalité ?
Gestion des
erreurs et → Divulgâchage : NON !
exceptions

Interfaces
graphiques En réalité, paradigme “idéal” trop contraignant, empêchant les optimisations matérielles.
Modèle d’exécution réellement implémenté par la JVM : le JMM 403 .
Concurrence
Introduction
Threads en Java
Dompter le JMM
APIs de haut niveau
Seule garantie : sous condition, ce qu’on observe est indistinguable du paradigme “idéal”.

402. Hors synchronization, évidemment.


Analyse 403. Java Memory Model
Compléments
en POO Modèle de mémoire Java et optimisations
Aldric Degorre Cohérence de la mémoire

Introduction

Style

Objets et Réalité physique : chaque cœur de CPU dispose de son propre cache 404 de mémoire.
classes

Types et Interprétation : Chaque thread utilise potentiellement un cache de mémoire différent.


polymorphisme
Ainsi, les données partagées existent en de multiples copies pas forcément à jour.
Héritage

Généricité (on parle de problèmes de visibilité des changements et de cohérence de la mémoire)


Gestion des
erreurs et Solution naïve : répercuter immédiatement les changements dans tous les caches
exceptions
immédiatement.
Interfaces
graphiques
Problème : cette opération est coûteuse et supprime le bénéfice du cache.
Concurrence
Introduction
Threads en Java
→ le JMM : ne garantit donc pas une cohérence parfaite (mais un minimum
Dompter le JMM
APIs de haut niveau
quand-même... )

Analyse 404. Mémoire locale propre au cœur, plus proche physiquement et plus rapide que la mémoire centrale.
Compléments
en POO Modèle de mémoire Java et optimisations
Aldric Degorre Consistence de la mémoire

Introduction

Style
Par exemple, dans le programme suivant :
Objets et public class ThreadConsistence extends Thread{
classes static boolean x = false, y = false;
Types et
polymorphisme public void run(){
Héritage if (x || !y) { x = true; y = true; } else System.out.println("Bug␣!");
Généricité
// Affiche "Bug !" si on trouve y vrai alors que x est faux
}
Gestion des
erreurs et
exceptions public static void test(int nthreads) throws InterruptedException {
for (int i = 0; i < nthreads; i++) new ThreadConsistence().start();
Interfaces
graphiques }
}
Concurrence
Introduction
Threads en Java
Dompter le JMM
APIs de haut niveau
L’appel test(100) peut afficher “Bug !”.
Par exemple : si un des threads finit d’exécuter “x = true; y = true;” et un autre
reçoit, dans son cache, la modification sur y mais pas sur x.
Exemple
Compléments
en POO Modèle de mémoire Java et optimisations
Aldric Degorre Réarrangement des instructions

Introduction

Style Réalité physique : Les CPU sont dotés de mécanismes permettant de réordonner des
Objets et
classes
instructions 405 qu’il sait devoir exécuter (afin de mieux occuper tous ses composants).
Types et
polymorphisme
Interprétation : l’ordre du programme n’est pas toujours respecté (même sur 1 thread).
Héritage En plus, optimisations différentes d’une architecture matérielle à une autre (p. ex :
Généricité comportement différent entre x86 et ARM) → ordre peu prévisible.
Gestion des
erreurs et
exceptions
Solution naïve : ajouter des barrières 406 partout dans le code compilé.
Interfaces
graphiques
Problème : vitesse d’exécution sous-optimale (le CPU n’arrive plus à donner autant de
Concurrence
travail à tous ses composants).
Introduction
Threads en Java → le JMM : ne garantit pas le respect exact de l’ordre du programme... mais promet que
Dompter le JMM
APIs de haut niveau certaines choses importantes restent bien ordonnées.

405. out-of-order execution


Analyse 406. Insruction spécifique prévue dans les CPU, justement pour empêcher le ré-ordonnancement.
Compléments
en POO Modèle de mémoire Java et optimisations
Aldric Degorre Réarrangement des instructions

Introduction

Style
Exemple :
Objets et
classes • Supposons qu’initialement x == 0 && y == 0. On veut exécuter :
Types et
polymorphisme • sur le thread 1 : (1) a = x; (2) y = 1;
Héritage • et, sur le thread 2 : (3) b = y; (4) x = 2;.
Généricité

Gestion des
• (1) arrive-avant (2) et (3) avant (4)
erreurs et
exceptions → à la fin, il semble impossible d’avoir à la fois a == 2 et b == 1.
Interfaces
graphiques
• Or c’est pourtant possible !
Concurrence En effet, sur chaque thread isolé, inverser les 2 instructions ne change pas le
résultat. Comme il n’y a pas de synchronisation, rien n’interdit donc ces inversions.
Introduction
Threads en Java

Il est donc possible d’exécuter les 4 instructions dans l’ordre suivant :


Dompter le JMM
APIs de haut niveau

y = 1; x = 2; a = x; b = y;.

Exemple
Compléments
en POO Modèle de mémoire Java et optimisations
Aldric Degorre Principe général du JMM

Introduction

Style

Objets et
classes Entre 2 points de synchronisation, toute optimisation est autorisée tant qu’elle ne change
Types et pas le comportement observable d’un thread qui tournerait en isolation 407 .
polymorphisme

Héritage Donc
Généricité
• mono-thread → aucune différence visible due à ces optimisations ;
Gestion des
erreurs et
exceptions • mais multi-thread → différences possibles si synchronisation insuffisante 408 .
Interfaces
graphiques → au programmeur de faire en sorte d’avoir une synchronisation suffisante afin que ces
Concurrence
Introduction
optimisations ne soient pas un problème.
Threads en Java
Dompter le JMM
APIs de haut niveau

407. À définir plus précisément.


408. Idem !
Compléments
en POO Ordre arrivé-avant et ordre d’exécution
Aldric Degorre Définitions

Introduction
2 relations d’ordre à considérer (tous deux relatifs à une exécution donnée 409 ) :
Style

Objets et • Ordre arrivé-avant :


classes

Types et • relation d’ordre partiel 410 sur les instructions exécutées d’un programme.
polymorphisme
• → indique l’ordre “causal” des évènements (et non chronologique).
Héritage
• Toute modification causée par ce qui arrive-avant est ”vue” par ce qui arrive-après.
Généricité
• dans un même thread : ordre total correspondant à l’ordre des instructions (appelé
Gestion des
erreurs et ordre du programme).
exceptions • entre 2 threads : événement e de thread a arrive-avant événement f de thread b
Interfaces
graphiques
seulement si synchronisation entre a et b qui arrive-après e dans a et avant f dans b.
Concurrence
Introduction
• Ordre d’exécution : ordre chronologique réel d’exécution des instructions.
Threads en Java
Dompter le JMM On voudrait que, dans une exécution correcte, cet ordre-là respecte “notre” logique.
APIs de haut niveau

Qu’est-ce qui relie ces 2 ordres ?


409. Par opposition à un programme donné.
Supplément 410. Ceci implique la transitivité : si A arrive-avant B et B arrive-avant C, alors A arrive-avant C.
Compléments
en POO Ordre arrivé-avant et ordre d’exécution
Aldric Degorre Relation entre les 2 ordres

Introduction

Style
Pour une même exécution :
Objets et
classes
• Ordre d’exécution = réalité objective, non interprétée, de celle-ci.
Types et
polymorphisme
• Ordre arrivé-avant = mêmes éléments, mais ordonnés en fonction de l’ordre du
Héritage
programme et des synchronisations. → interprétation, abstraction de la réalité.
Généricité

Gestion des
erreurs et
En réalité :
exceptions

Interfaces • ordre arrivé-avant : défini sans ambiguïté et facile à déduire à partir d’un code
graphiques
source et d’un ensemble d’instructions effectivement exécutées.
Concurrence
Introduction
Threads en Java
• ordre d’exécution : malgré définition intuitive, ordre difficile à prévoir, dépendant
Dompter le JMM
APIs de haut niveau des optimisations opérées par le CPU. Il ne raffine pas forcément l’ordre
arrivé-avant d’une exécution donnée, ni même l’ordre du programme.

Supplément
Compléments
en POO Accès en compétition
Aldric Degorre

Introduction

Style

Objets et
classes Variable partagée : variable accessible par plusieurs threads.
Types et
polymorphisme Tout attribut est (à moins de prouver le contraire) une variable partagée. Les autres
Héritage variables (locales ou paramètres de méthodes) ne sont jamais partagées. 411
Généricité
Accès conflictuels : dans une exécution, 2 accès à une même variable sont conflictuels
Gestion des
erreurs et si au moins l’un des deux est en écriture.
exceptions

Interfaces Accès en compétition (data race) : 2 accès conflictuels à une variable partagée, tels que
graphiques

Concurrence
l’un n’arrive-pas-avant l’autre.
Introduction
Threads en Java
Dompter le JMM
APIs de haut niveau

411. Mais les attributs de l’objets référencé peuvent être partagés !


Compléments
en POO Accès en compétition
Aldric Degorre Exemples

Introduction

Style Programme avec accès en compétition : Programme sans accès en compétition :


Objets et class Boite { class BoiteSynchro {
classes int x ; private int x ;
} p u b l i c synchronized i n t getX ( ) {
Types et
return x ;
polymorphisme
public class Competition { }
Héritage p u b l i c s t a t i c v o i d main ( S t r i n g a r g s [ ] ) { p u b l i c synchronized void setX ( i n t x ) {
B o i t e b = new B o i t e ( ) ; this . x = x ;
Généricité new T h r e a d ( ( ) −> { b . x = 1 ; } ) . s t a r t ( ) ; }
new T h r e a d ( ( ) −> { }
Gestion des System . o u t . p r i n t l n ( b . x ) ;
erreurs et }) . start () ; public class PasCompetition {
exceptions } p u b l i c s t a t i c v o i d main ( S t r i n g a r g s [ ] ) {
B o i t e S y n c r o b = new B o i t e S y n c h r o ( ) ;
Interfaces } new T h r e a d ( ( ) −> {
graphiques b . setX ( 1 ) ;
Concurrence }) . start () ;
new T h r e a d ( ( ) −> {
Introduction Ici, rien n’impose que la lecture de b.x System . o u t . p r i n t l n ( b . g e t X ( ) ) ;
Threads en Java
Dompter le JMM arrive-avant son affectation ou bien le }
}) . start () ;
APIs de haut niveau
contraire.
}

Exemple
Compléments
en POO Programme correctement synchronisé
Aldric Degorre

Introduction
Exécution sequentiellement cohérente : exécution
Style

Objets et • qui suit un ordre total,


classes

Types et • respectant l’ordre du programme,


polymorphisme

Héritage • et telle que pour toute lecture d’un emplacement mémoire, la dernière écriture, dans
Généricité le passé, sur cet emplacement est prise en compte.
Gestion des
erreurs et
exceptions Exemple : si x = 0, x = 1 et println(x) s’exécutent dans cet ordre et qu’entre
Interfaces x = 1 et println(x) il n’y a pas d’affectation à x, alors c’est bien 1 qui s’affiche.
graphiques

Concurrence Programme correctement synchronisé 412 : se dit d’un programme dont toute exécution
Introduction
Threads en Java
sequentiellement cohérente est sans accès en compétition.
Dompter le JMM
APIs de haut niveau

412. Ça correspond à la notion d’“isolation” évoquée plus haut : ce critère est équivalent au fait qu’aucune
écriture simultanée ne puisse avoir lieu sur des données lues par un thread entre deux points de
Supplément synchronisation.
Compléments
en POO Modèle d’exécution
Aldric Degorre La vérité, enfin !

Introduction

Style L’ordre d’exécution d’un programme peut être ou paraître imprévisible, mais au moins ce
Objets et
classes
qui suit est garanti par le JMM (donc la JVM) :
Types et
polymorphisme Le critère :
Héritage Si le programme est correctement synchronisé 413 ,
Généricité alors son exécution est indiscernable d’une exécution séquentiellement cohérente.
Gestion des
erreurs et
exceptions
Voici enfin notre critère de “synchronisation suffisante” !
Interfaces Concrètement, pour que les optimisations ne provoquent pas d’incohérences, il “suffit”
graphiques

Concurrence
donc de supprimer les compétitions.
Introduction
Threads en Java Remarque : le plus dur reste de trouver quels accès sont en compétition...
Dompter le JMM
APIs de haut niveau

413. Note : si la synchronisation est incorrecte, cela ne veut pas dire qu’on ne sait rien. En fait, la
spécification donne tout un ensemble de règles basées sur un critère de causalité... règles trop compliquées
Résumé et donnant des garanties trop faibles pour être raisonnablement utilisables en pratique.
Compléments
en POO Éviter les problèmes, version courte
Aldric Degorre

Introduction

Style

Objets et
classes

Types et
polymorphisme

Héritage Si vous pouvez prouver que tout accès en compétition à vos variables partagées est
Généricité
impossible, alors vous serez pas importuné par les optimisations !
Gestion des
erreurs et
exceptions

Interfaces
graphiques

Concurrence
Introduction
Threads en Java
Dompter le JMM
APIs de haut niveau

Résumé
Compléments
en POO Supprimer les compétitions
Aldric Degorre

Introduction
Comment éviter les compétitions ?
Style

Objets et
classes
• éviter de partager les variables quand ce n’est pas nécessaire → préférer les
Types et variables locales (jamais partagées) aux attributs ;
polymorphisme

Héritage
• quand ça suffit, privilégier les données partagées en lecture seule → privilégier les
Généricité structures immuables (voir ci-après) ;
Gestion des
erreurs et
• sinon, renforcer la relation arrivé-avant :
exceptions
• utiliser les mécanismes de synchronisation déjà présentés,
Interfaces
graphiques
• marquer des attributs comme final ou volatile (voir ci-après) ;
Concurrence
Introduction
• utiliser des classes déjà écrites et garanties “thread-safe”.
Threads en Java
Dompter le JMM
APIs de haut niveau
• Souvent, rien de tout ça ne convient : on peut avoir besoin d’attributs modifiables
sans synchronisation ! Mais il faudra s’assurer qu’un seul thread peut y accéder.
Compléments
en POO Les mot-clés volatile et final (1)
Aldric Degorre
Attributs volatils : un attribut déclaré avec volatile garantit 414 :
Introduction

Style • que tout accès en lecture se produisant, chronologiquement, après un accès en


Objets et
classes
écriture, arrive-après celui-ci.
Types et (concrètement : cet attribut n’est jamais mis dans le cache local d’un thread)
polymorphisme

Héritage
• que tout accès simple en lecture ou écriture est atomique (même pour long et
Généricité double).
Gestion des
erreurs et
exceptions
→ comme si cet attribut était accédé via des accesseurs synchronized.
Interfaces
graphiques
Attributs finaux :
Concurrence
Introduction
• déjà vu : un attribut final ne peut être affecté qu’une seule fois (lors de
Threads en Java
Dompter le JMM
l’initialisation de la classe ou de l’objet).
APIs de haut niveau
• garantie supplémentaire : comme pour volatile, tout accès en lecture à un
attribut final arrive-après son initialisation (unique, pour final).
414. Ceci ne concerne pas le contenu de l’éventuel objet référencé.
Compléments
en POO Les mot-clés volatile et final (2)
Aldric Degorre

Introduction

Style
Technique infaillible : tous les attributs volatile (ou final) =⇒ accès en
Objets et
compétition impossible. Cependant pas idéale car :
classes

Types et • non réaliste : un programme utilise des classes faites par d’autres personnes ; 415
polymorphisme

Héritage • non efficace : volatile empêche les optimisations ⇒ exécution plus lente. 416
Généricité

Gestion des
En plus, volatile ne permet pas de rendre les méthodes atomiques ⇒ entrelacements
erreurs et
exceptions
toujours non maîtrisés ⇒ volatile ne suffit pas toujours pour tout.
Interfaces
graphiques
Exemple : avec volatile int x = 0;, si on exécute 2 fois en parallèle x++, on peut
Concurrence
toujours obtenir 1 au lieu de 2.
Introduction
Threads en Java
Dompter le JMM

415. Mais on peut encapsuler leurs instances dans des classes à méthodes synchronisées... au prix
APIs de haut niveau

d’encore un peu moins de performance.


416. Remarque : pour final, la question ne se pose qu’au début de la vie de l’objet. À ce stade, accéder à
Discussion une ancienne version de l’attribut n’aurait aucun sens. L’optimisation serait nécessairement fausse.
Compléments
en POO Les objets immuables
Aldric Degorre

Introduction • Rappel : immuable = non modifiable. Le terme s’applique aux objets et, par
Style extension, aux classes dont les objets immuables sont les instances.
Objets et
classes • Pas d’accès en écriture ⇒ pas d’accès en compétition ⇒ pas de risque
Types et d’incohérence de cache (sur le contenu d’un tel objet).
polymorphisme

Héritage • Mieux : immuable =⇒ thread-safe. L’objet ne changeant pas d’état, l’utilisation qui
Généricité en est faite dans un thread n’influe pas sur l’utilisation dans un autre thread.
Gestion des
erreurs et • Se “transforme” à l’aide de fonctions pures (prend x, calcule x′ sans modifier x et
exceptions
retourne x′ ). Le résultat peut être publié dans une variable partagée ou passé à un
Interfaces
graphiques autre thread par un autre mécanisme.
Concurrence
Introduction
Exemple : SharedRessources.x = f(g(h(SharedResources.x)));
Threads en Java
Dompter le JMM • Inconvénient : implique d’allouer un nouvel objet pour chaque étape de calcul.
(coûteux, mais pas forcément excessif 417 )
APIs de haut niveau

417. Notamment si l’escape analysis détermine que l’objet n’est que d’usage local → la JVM l’alloue en pile.
Cela dit, ceci ne concerne que les calculs intermédiaires car la variable partagée est stockée dans le tas.
Compléments
en POO Variables atomiques
Aldric Degorre

Introduction

Style java.util.concurrent.atomic propose un certain nombre de classes de variables


Objets et
classes
atomiques (classes mutables thread-safe).
Types et
polymorphisme • Exemples : AtomicBoolean, AtomicInteger, AtomicIntegerArray, …
Héritage
• Leurs instances représentent des booléens, des entiers, des tableaux d’entiers, ...
Généricité

Gestion des • Accès simples : comportement similaire aux variables volatiles.


erreurs et
exceptions • Disposent, en plus, d’opérations plus complexes et malgré tout atomiques
Interfaces
graphiques (typiquement : incrémentation). 418
Concurrence
Introduction
Threads en Java
Dompter le JMM
APIs de haut niveau
418. L’accès atomique est garanti sans synchronisation, grâce à des appels à des instructions dédiées des
processeurs, telles que CAS (compare-and-set). Ainsi ces classes ne sont en réalité pas implémentées en
Java, car elles sont compilées en tant que code spécifique à l’architecture physique (celle sur laquelle
tourne la JVM).
Compléments
en POO Autres objets thread-safe
Aldric Degorre

Introduction

Style

Objets et
classes

Types et
polymorphisme Nombre de classes de l’API sont signalées comme thread-safe. En particulier, il peut être
Héritage utile de rechercher la documentation des collections concurrentes (package
Généricité java.util.concurrent).
Gestion des
erreurs et Regardez les différentes implémentations de BlockingQueue et de ConcurrentMap,
exceptions
par exemple.
Interfaces
graphiques

Concurrence
Introduction
Threads en Java
Dompter le JMM
APIs de haut niveau
Compléments
en POO Les nombreux inconvénients de l’API threads
Aldric Degorre

Introduction

Style
Utiliser directement les threads et les moniteurs → nombreux inconvénients :
Objets et
classes
• Chaque thread utilise beaucoup de mémoire. Et les instancier prend du temps.
Types et
polymorphisme
• Trop de threads → changements de contexte fréquents (opération coûteuse).
Héritage • Nécessité de communiquer par variables partagées → risques d’accès en
Généricité
compétition (et donc d’incohérences)
Gestion des
erreurs et
exceptions
• En cas de synchronisation mal à-propos, risque de blocage.

Heureusement : de nombreuses API de haut niveau 419 aident le programmeur à


Interfaces
graphiques

Concurrence contourner ces écueuils.


Introduction
Threads en Java
Dompter le JMM → il est souvent préférable de travailler avec celles-ci, plutôt que directement avec les
APIs de haut niveau
threads et les moniteurs.

Analyse 419. programmées par dessus les threads et les moniteurs


Compléments
en POO APIs pour la concurrence
Aldric Degorre Schéma de base, pour limiter le nombre de threads

Introduction Idée : réutiliser un même thread pour plusieurs exécuter plusieurs tâches tour à tour.
Style

Objets et
Exécuteur :
classes

Types et • objet qui gère un certain ensemble de threads et distribue des tâches sur ceux-ci.
polymorphisme

Héritage • Évite de créer plus de threads que nécessaire 420 .


Généricité
• Évite de détruire un thread aussitôt qu’il est libre (pour éviter d’en re-créer).
Gestion des
erreurs et
exceptions
• Gère la distribution des tâches sur ses threads, selon politique définie.
Interfaces
graphiques Tâche :
Concurrence
Introduction • séquence d’instructions (en pratique : une fonction) à exécuter sur un thread
Threads en Java

• en principe une tâche n’est pas interruptible 421 , sauf lorsqu’elle effectue un appel
Dompter le JMM
APIs de haut niveau

bloquant (multi-tâche coopératif)


420. Selon politique de l’exécuteur. Plusieurs possibles. Par exemple : nb. max. threads ≤ nb. cœurs.
421. Son exécution sur le thread ne l’est pas, mais le thread lui-même reste interruptible.
Compléments
en POO APIs pour la concurrence
Aldric Degorre Différents styles d’APIs pour les tâches

Introduction
Pour synchroniser et faire communiquer des tâches interdépendantes, 2 styles d’API :
Style

Objets et (dans les 2 cas, un mécanisme de passage de message permet d’éviter les variables
classes
partagées)
Types et
polymorphisme

Héritage
• API bloquante : appel de méthode bloquante pour attendre la fin d’une autre tâche
Généricité (comme join pour les threads) et obtenir son résultat (si applicable).
Gestion des Exemple :
erreurs et
exceptions
ForkJoinTask<Integer> f = ForkJoinTask.adapt(() -> scanner.nextInt());
Interfaces ForkJoinTask.adapt(() -> {
graphiques
f.fork(); // lancement d'une sous-tâche
Concurrence System.out.println(f.join()); // appel bloquant avec récupération du résultat
Introduction
Threads en Java
}).fork(); // lancement de la tâche principale
Dompter le JMM
APIs de haut niveau

Dans le JDK : Thread, Future et ForkJoinTask suivent ce principe. 422


422. Dans les bibliothèques tierces, citons aussi : les fibres (cf. bibliothèque Quasar) et certaines
implémentations d’acteurs (en particulier celle de Quasar), ...
Compléments
en POO APIs pour la concurrence
Aldric Degorre Différents styles d’APIs pour les tâches

Introduction

Style
• API non bloquante : une tâche qui dépend d’un résultat fourni par une autre est
Objets et passée en tant que fonction de rappel (callback) 423 . Cette dernière sera déclenchée
classes
par l’arrivée du résultat attendu (plus généralement : un évènement). Exemple :
Types et
polymorphisme
CompletableFuture
Héritage // tâche 1 : d'abord programme la lecture d'un Scanner
Généricité .supplyAsync(scanner::nextInt)
// tâche 2 : dès qu'un entier est fourni, affiche-le
Gestion des
erreurs et .thenAccept(System.out::println);
exceptions

Interfaces
graphiques
Dans le JDK : Swing, JavaFX 424 , CompletableFuture, Stream, Flow
Concurrence
Introduction
(Java 9). 425
Threads en Java
Dompter le JMM 423. Sur le principe, une fonction de première classe → en Java traditionnel un objet avec une méthode
APIs de haut niveau
dédiée ; en Java moderne, une lambda-expression.
424. JavaFX est en fait séparé du JDK depuis Java 11, désormais développé au sein du projet OpenJFX.
425. Et dans les bibliothèques tierces : certaines implémentations des acteurs (ex : Akka), Reactive Streams
(autres implémentations que Java Flow ), ...
Compléments
en POO Interfaces Executor et Runnable
Aldric Degorre
• En Java, les exécuteurs sont les instances de l’interface Executor :
Introduction

Style public interface Executor { void execute(Runnable command); }


Objets et
classes La commande unExecutor.execute(unRunnable) exécute la méthode run()
Types et
polymorphisme
de unRunnable. Ainsi, dans ce cas, les tâches sont des instances de Runnable.
Héritage • En pratique, on utilise la sous-interface ExecutorService (voir la suite).
Généricité
• Un exemple :
Gestion des
erreurs et // instanciation d'un exécuteur gérant un thread unique :
exceptions
ExecutorService executor = Executors.newSingleThreadExecutor();
Interfaces
graphiques
// lancement d'une tâche (décrite par la lambda expression, type inféré Runnable)
Concurrence executor.execute(() -> { System.out.println("bla␣bla␣bla"); });
Introduction
Threads en Java
Dompter le JMM // on détruit les threads dès que tout est terminé
APIs de haut niveau executor.shutdown();

Implémentations diverses, utilisant un ou plusieurs threads (thread pool).


Compléments
en POO Interfaces ExecutorService, Callable<V> et Future<V>
Aldric Degorre
• ExecutorService : étend Executor en y ajoutant :
Introduction

Style
• <T> Future<T> submit(Callable<T> task) : programme une tâche.
Objets et
• Des méthodes pour demander et/ou attendre la terminaison des tâches en cours.
classes

Types et
• Callable<V> est comme Runnable, mais sa méthode retourne un résultat :
polymorphisme
public interface Callable<V> { V call(); }
Héritage

Généricité

Gestion des
• Future<V> = objets pour accéder à un résultat de type V promis dans le futur :
erreurs et
exceptions public interface Future<V> {
Interfaces
boolean cancel(boolean mayInterruptIfRunning);
graphiques V get() throws InterruptedException, ExecutionException;
Concurrence
V get(long timeout, TimeUnit unit) throws InterruptedException,
Introduction ExecutionException, TimeoutException;
Threads en Java boolean isCancelled();
Dompter le JMM
APIs de haut niveau
boolean isDone();
}

Les méthodes get sont bloquantes jusqu’à disponibilité du résultat.


Compléments
en POO Interfaces ExecutorService, Callable<V> et Future<V>
Aldric Degorre Usage (schéma général)

Introduction

Style

Objets et
classes

Types et
polymorphisme ExecutorService es = ...;
...
Héritage
Callable<String> task = ...;
Généricité // task sera executé dans thread choisi par es :
Gestion des Future<String> result = es.submit(task);
erreurs et ...
exceptions
// le programme attend puis affiche le résultat :
Interfaces System.out.println("Résultat␣:␣" + result.get());
graphiques

Concurrence
Introduction
Threads en Java
Dompter le JMM
APIs de haut niveau

Exemple
Compléments
en POO Interfaces ExecutorService, Callable<V> et Future<V>
Aldric Degorre Exemple plus complet

Introduction public class TestCall implements Callable<Integer> {


private final int x;
Style
public TestCall(int x) { this.x = x; }
Objets et @Override public Integer call() { return x; }
classes
}
Types et
polymorphisme
public class Exemple {
Héritage public static void main(String[] args){
Généricité ExecutorService executor = Executors.newSingleThreadExecutor();
Future<String> futur1 = executor.submit(new TestCall(1));
Gestion des
erreurs et Future<String> futur2 = executor.submit(new TestCall(2));
exceptions try { System.out.println(futur1.get() + futur2.get()); } // affiche "3"
Interfaces catch(ExcecutionException | InterruptedException e) { ... }
graphiques finally { executor.shutdown(); }
Concurrence }
Introduction }
Threads en Java
Dompter le JMM
APIs de haut niveau
Ici, l’exécuteur exécute les 2 tâches l’une après l’autre (un seul thread utilisé), mais en
même temps que la méthode main() continue à s’exécuter.
Exemple Cette dernière finit par attendre les résultats des 2 tâches pour les additionner.
Compléments
en POO Instancier des exécuteurs
Aldric Degorre

À l’aide de la classe Executors :


Introduction

Style • = bibliothèque de fabriques statiques d’ExecutorService.


Objets et
classes • static ExecutorService newSingleThreadExecutor() : crée un
Types et
polymorphisme ExecutorService utilisant un worker thread unique.
Héritage • static ExecutorService newCachedThreadPool() : crée un exécuteur
Généricité
dont les threads du pool se créent, à la demande, sans limite, mais sont réutilisés
Gestion des
erreurs et s’ils redeviennent disponibles.
exceptions

Interfaces
• static ExecutorService newFixedThreadPool(int nThreads) :
graphiques
même chose, mais avec limite fixée à n threads 426 .
Concurrence

On peut aussi utiliser directement les constructeurs de ThreadPoolExecutor ou de


Introduction
Threads en Java

ScheduledThreadPoolExecutor 427 (nombreuses options).


Dompter le JMM
APIs de haut niveau

426. Choisir n en rapport avec le nombre d’unités de calcul/cœurs.


427. Implémente ScheduledExecutorService, permettant de programmer des tâches périodiques et/ou
Détails différées. Les futurs retournés implémentent ScheduledFuture<V>. Regardez la documentation.
Compléments
en POO Les limites de ThreadPoolExecutor
Aldric Degorre (et des fabriques newCachedThreadPool() et newFixedThreadPool())

Introduction

Style • But d’un thread pool = réduire le nombre de threads → petit nombre de threads.
Objets et
classes • Or les threads bloqués (par appel bloquant, comme f.get(), au sein de la tâche)
Types et
polymorphisme
ne sont pas réattribuables à une autre tâche 428 .
Héritage → moins de threads disponibles dans le pool → ralentissement.
Généricité
• Cas extrême : si grand nombre de tâches concurrentes avec interdépendances, il
Gestion des
erreurs et arrive que tout le pool soit bloqué par des tâches en attente de tâches bloquées ou
exceptions
non démarrées → rien ne viendra débloquer la situation.
Interfaces
graphiques Cette situation s’appelle un thread starvation deadlock 429 .
Concurrence

Comment concilier pool de taille bornée et garantie d’absence de bloquage ?


Introduction
Threads en Java
Dompter le JMM
APIs de haut niveau
428. Il est impossible de “sortir” une tâche déjà en exécution sur un thread pour le libérer.
429. Du point de vue des threads, c’est bien un deadlock : dépendance cyclique entre threads.
Du point de vue des tâches, dépendance pas forcément cyclique, mais bloquage car multi-tâche
Discussion non-préemptif s’exécutant sur un nombre limité d’unités d’exécution.
Compléments
en POO Work-stealing strategy
Aldric Degorre Decription

Introduction

Style

Objets et
classes
Solution : stratégie de vol de travail (work stealing). Principe :
Types et
polymorphisme

Héritage
• une file d’attente de tâches par thread au lieu d’une commune à tout le pool ;
Généricité • tâches générées par une autre tâche → ajoutées sur file du même thread ;
Gestion des
erreurs et • quand un thread veut du travail, il prend une tâche en priorité dans sa file, sinon il en
exceptions

Interfaces
“vole” une dans celle d’un autre thread ;
graphiques
• le plus important : si le résultat attendu n’est pas disponible, get (et join)
Concurrence
Introduction exécute d’abord les tâches en file au lieu de bloquer le thread tout de suite.
Threads en Java
Dompter le JMM
APIs de haut niveau
Compléments
en POO Work-stealing stategy
Aldric Degorre Pourquoi ça marche

Introduction

Style

Objets et
classes

Types et
polymorphisme Le vol de travail assure qu’aucun thread n’est jamais bloqué en attente d’une tâche non
Héritage
démarrée.
Généricité

Gestion des =⇒ si tous les threads sont bloqués en attente d’une tâche, c’est par une autre tâche
erreurs et
exceptions bloquée ; c’est donc qu’il y avait une dépendance cyclique.
Interfaces
graphiques =⇒ si pas de dépendances cycliques, thread starvation deadlock impossible.
Concurrence
Introduction
Threads en Java
Dompter le JMM
APIs de haut niveau
Compléments
en POO Work-stealing strategy
Aldric Degorre Indications et contre-indications

Introduction

Style

Objets et
classes

Types et
• Petites tâches à dépendances acycliques (notamment, algorithmes récursifs)
polymorphisme
→ stratégie très intéressante (pas de thread starvation deadlock).
Héritage

Généricité • Tâches sans dépendances


Gestion des
erreurs et
→ stratégie inutile et plus lourde que la stratégie “naïve” 430 .
exceptions
• Tâches à dépendances cycliques
Interfaces
graphiques → deadlocks assurés (mais en fait... c’est inévitable).
Concurrence
Introduction
Threads en Java
Dompter le JMM
APIs de haut niveau

430. Sans compter qu’en Java, l’implémentation de celle-ci (ThreadPoolExecutor) est plus configurable
Discussion que l’implémentation de la work-stealing strategy (ForkJoinPool).
Compléments
en POO ForkJoinPool et ForkJoinTask
Aldric Degorre L’implémentation de la work-stealing strategy en Java

Introduction

Style

Objets et
classes
Ainsi Java propose :
Types et
polymorphisme
• la classe ForkJoinPool : implémentation d’Executor utilisant cette stratégie
Héritage

Généricité • la classe ForkJoinTask : tâches capables de générer des sous-tâches


Gestion des
erreurs et
(“fork()”) et d’attendre leurs résultats pour les utiliser (“join()”).
exceptions

Interfaces ForkJoinPool est considéré suffisament efficace pour que le thread pool par défaut
graphiques
(utilisé implicitement par plusieurs API concurrentes) soit une instance de cette classe.
Concurrence
Introduction
Threads en Java
Obtenir le thread pool par défaut : ForkJoinPool.commonPool().
Dompter le JMM
APIs de haut niveau
Compléments
en POO ForkJoinPool et ForkJoinTask
Aldric Degorre
• Méthodes de ForkJoinTask :
Introduction

Style
• ForkJoinTask<T> fork() : demande l’exécution de t et rend la main. Le résultat
Objets et
du calcul peut être récupéré plus tard en interrogeant l’objet retourné.
classes • T invoke() : pareil, mais attend que t soit finie et retourne le résultat.
Types et • T join() : attendre le résultat du calcul signifié par t (T get() existe aussi car
polymorphisme
ForkJoinTask<T> implémente Future<T>)
Héritage

Généricité fork et invoke exécutent la tâche dans le pool dans lequel elles sont appelées, si
Gestion des applicable, sinon dans le pool par défaut.
erreurs et
exceptions • Pour exécuter une tâche sur un ForkJoinPool précis, on appelle les méthodes
Interfaces suivantes (de la classe ForkJoinPool) sur le pool p cible :
graphiques

Concurrence
• <T> T invoke(ForkJoinTask<T> task) : demande l’exécution de task sur p
Introduction
Threads en Java
et retourne le résultat dès qu’elle se termine (appel bloquant).
Dompter le JMM • <T> ForkJoinTask<T> submit(ForkJoinTask<T> task) : idem, mais rend la
main tout de suite en retournant un futur, permettant de récupérer le résultat plus tard.
APIs de haut niveau

• void execute(ForkJoinTask<?> task) : idem, aussi non bloquant, mais on ne


récupère pas le résultat.
Détails
Compléments
en POO ForkJoinPool et ForkJoinTask
Aldric Degorre
Et ForkJoinTask ?
Introduction

Style • Classe abstraite. Ses objets sont les tâches exécutables par ForkJoinPool.
Objets et • On préfère étendre une de ses sous classes (abstraites aussi) 431 :
classes

Types et
• RecursiveAction : tâche sans résultat (exemple : modifier les feuilles d’un arbre)
polymorphisme • RecursiveTask<V> : tâche calculant un résultat de type V (exemple : compter les
Héritage feuilles d’un abre)
Généricité
Dans les 2 cas, on étend la classe en définissant la méthode compute() qui décrit
Gestion des
erreurs et les actions effectuées par la tâche :
exceptions
• pour RecursiveAction : protected void compute() ;
Interfaces
graphiques
• pour RecursiveTask<V> : protected V compute().
Concurrence
Introduction
• 3 fabriques statiques ForkJoinTask.adapt(...) 432 permettent de créer des
Threads en Java
Dompter le JMM
tâches à partir de Runnable et de Callable<V>.
APIs de haut niveau
ForkJoinTask.adapt(() -> { /* instructions */ }).fork() // sympa avec les lambdas !

431. Ces deux classes servent juste à faciliter l’implémentation.


Détails 432. Le nom adapt provient du patron adapter.
Compléments
en POO ForkJoinPool et ForkJoinTask
Aldric Degorre Et car rien ne vaut un exemple...

Introduction
La tâche récursive :
Style

Objets et class Fibonacci extends RecursiveTask<Integer> {


classes final int n;
Types et
polymorphisme Fibonacci(int n) { this.n = n; }
Héritage
@Override protected Integer compute() {
Généricité
if (n <= 1) return n;
Gestion des Fibonacci f1 = new Fibonacci(n - 1);
erreurs et
exceptions
f1.fork();
Fibonacci f2 = new Fibonacci(n - 2);
Interfaces
return f2.compute() + f1.join();
graphiques
}
Concurrence }
Introduction
Threads en Java
Dompter le JMM
APIs de haut niveau
Et l’appel initial (dans main()) :
System.out.println((new ForkJoinPool()).invoke(new Fibonacci(12)));

Exemple
Compléments
en POO Une alternative : CompletableFuture (Java 8)
Aldric Degorre

Introduction

Style

Objets et •
classes class CompletableFuture<T> implements Future<T>, CompletionStage<T>
Types et
polymorphisme
• CompletionStage<T> propose des méthodes pour ajouter des callbacks à
Héritage

Généricité
exécuter lors de la complétion de la tâche.
Gestion des
erreurs et → changement de paradigme : plus d’appels bloquants dans les tâches élémentaires,
exceptions

Interfaces
mais dépendances maintenant décrites en dehors de celles-ci.
graphiques

Concurrence
Le programme appelant compose ces tâches entre elles pour décrire une “recette” 433
Introduction qu’il soumet au thread pool (et donc n’effectue pas non plus d’appel bloquant).
Threads en Java
Dompter le JMM
APIs de haut niveau

433. Les savants parlent de “monade”.


Compléments
en POO Une alternative : CompletableFuture (Java 8)
Aldric Degorre Exemple : de ForkJoinTask à CompletableFuture

Introduction

Style

Objets et Rf rf = Boulot.f();
classes
Future<Rg> frg = ForkJoinTask.adapt(() -> Boulot.g(rf)).fork();
Types et Rh rh = Boulot.h(rf);
polymorphisme
Ri ri = Boulot.i(rh, frg.join());
Héritage System.out.println("Résultat␣:␣" +ri);
Généricité

Gestion des
erreurs et Devient :
exceptions
CompletableFuture<Rf> cff = CompletableFuture.supplyAsync(Boulot::f);
Interfaces
graphiques CompletableFuture<Rg> cfg = cff.thenApplyAsync(Boulot::g);
CompletableFuture<Rh> cfh = cff.thenApply(Boulot::h);
Concurrence
Introduction
CompletableFuture<Ri> cfi = cfg.thenCombine(cfh, Boulot::i);
Threads en Java cfi.thenAccept(ri -> { System.out.println("Résultat␣:" + ri); });
Dompter le JMM
APIs de haut niveau

Exemple

You might also like