Professional Documents
Culture Documents
en POO
Aldric Degorre
Compléments en POO
Introduction
Style
Objets et
classes Aldric Degorre
Types et
polymorphisme
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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)
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++;
...
}
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"
)
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.
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
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
Interfaces
graphiques
Concurrence
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
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
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
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
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
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
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
Types et
Un membre peut être
polymorphisme
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
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
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
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).
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
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
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)
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
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
}
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
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
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é.
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
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
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
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
Types et • toutes les primitive wrapper classes : Boolean, Char, Byte, Short, Integer,
polymorphisme
Long, Float et Double ;
Héritage
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 ?
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
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
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
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
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
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
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
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
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
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)
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
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
• 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
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
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)
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
Introduction
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
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)
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
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
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
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
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
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
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)
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
• 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)
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
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
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
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
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
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
Interfaces
graphiques
Concurrence
Détails
Compléments
en POO Surcharge
Aldric Degorre Résolution (simplifiée... )
Introduction
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
Exemple
Compléments
en POO Surcharge
Aldric Degorre Pourquoi elle ne permet que du polymorphisme “faible”
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
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
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
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é
Introduction
Style
Objets et
classes
• ∃ 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
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
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
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
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
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
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
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
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
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
de membre, même “staticité”, même type ou sous-type... ), puis dans les types
Héritage des membres
Héritage des membres
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
Objets et
Quelle définition de f utiliser, quand on appelle f(x1, x2, ...), avec f statique ?
classes
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
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
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é
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
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...
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
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)
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)
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
à 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
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
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
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
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
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
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
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
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
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
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
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
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
Concurrence
Introduction
Style
Objets et
classes
Map
Types et
polymorphisme
Gestion des
erreurs et
exceptions Chacune de ces interfaces possède une ou plusieurs implémentations.
Interfaces
graphiques
Concurrence
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
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
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
Types et Exemple :
polymorphisme
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
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
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
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
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
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
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
Types et
polymorphisme
Sur un exemple :
Héritage
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
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
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
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
Gestion des
erreurs et Ceux-ci sont de la forme : ∀T [(∀B, ∈ BornesSup(T), T <: B) ⇒ BienType(progGen(T))] .
exceptions
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
Types et // l1: ceci est en fait interdit... mais supposons que ça passe...
polymorphisme
List<Voiture> listVoiture = listVoitureSP;
Héritage
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
Interfaces → Les tableaux sont covariants (l1 autorisé) : [A <: B] =⇒ [A[] <: B[]].
graphiques
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”
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
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
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
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]
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
Introduction
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)
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
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)
Ou encore :
Lambda-expressions
Les “streams”
Wildcards
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
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
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
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
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é :
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
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
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
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
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
Types et Exemples :
polymorphisme
Héritage
•
x -> x + 2
Généricité
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,
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
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
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
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
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
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
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,
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
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
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
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
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
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
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
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,
Introduction
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é
Introduction
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
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
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,
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
• 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
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”
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
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
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
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,
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,
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
Gestion des
erreurs et
exceptions
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
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
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
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
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
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) ?
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
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
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
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
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é
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
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
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
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
Héritage
• syntaxe : try (Resource r = ? /*expr */){ /*instr */? }
Généricité
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
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
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)
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
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
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
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
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é
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
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
Introduction
public class Mail {
Style
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
→ idéales, soit pour les erreurs à traitement non local 335 , soit pour les erreurs pour
Autres solutions
Discussion et stratégies
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
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
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
Introduction
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 .
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
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
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
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
Héritage
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
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
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
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
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é
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
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
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
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
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
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
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
Introduction
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
Introduction
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
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
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
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
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)
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
Introduction
Qu’est-ce qui est affiché quand on exécute le programme suivant ?
Style
Introduction
Style
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
395. À suivre...
Compléments
en POO Avec la classe Thread : méthode join()
Aldric Degorre
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
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
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
Types et
polymorphisme cpt.incremente();
Héritage
prise du moniteur
Généricité
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
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
• 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
Introduction
Un dernier avertissement : la synchronisation doit rester raisonnable !
Style
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
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”.
Introduction
Style
Objets et Réalité physique : chaque cœur de CPU dispose de son propre cache 404 de mémoire.
classes
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.
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
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
Introduction
2 relations d’ordre à considérer (tous deux relatifs à une exécution donnée 409 ) :
Style
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
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
Introduction
Exemple
Compléments
en POO Programme correctement synchronisé
Aldric Degorre
Introduction
Exécution sequentiellement cohérente : exécution
Style
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
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
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
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.
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
• en principe une tâche n’est pas interruptible 421 , sauf lorsqu’elle effectue un appel
Dompter le JMM
APIs de haut niveau
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
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
• <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();
}
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
Interfaces
• static ExecutorService newFixedThreadPool(int nThreads) :
graphiques
même chose, mais avec limite fixée à n threads 426 .
Concurrence
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
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
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
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
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 !
Introduction
La tâche récursive :
Style
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
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