La POO par l’utilisation #
Pour ce chapitre, les exemples seront exécutés dans le jshell
. Utilisez-le également pour apprendre à manipuler vos premiers objets.
Nous allons découvrir plusieurs classes appartenant à la librairie standard. Nous allons volontairement commencer par une classe mal réalisée pour aborder ensuite des classes connues et de meilleures qualité. L’objectif pédagogique est de:
- comprendre comment utiliser des objets avant de créer nos propres classes
- réfléchir à ce qui fait qu’une classe est de bonne qualité
Le mot type sera employé de manière générale pour désigner une classe ou une interface.
Utilisation de la (mauvaise) classe Date
#
Nous allons commencer par manipuler des dates. Nous allons commencer par la classe Date
du package java.util.Date
. Cette classe est maintenant dépréciée (deprecated), mais il s’agit d’un très bon exemple d’un très mauvais design de classe.
Danger
La classejava.util.Date
est utilisée à des fins pédagogiques uniquement. N’utilisez jamais cette classe. Nous verrons une version plus moderne de celle-ci par la suite.
Le code ci-dessous permet d’instancier (de créer) un objet de la classe Date
.
|
|
Dans cet exemple, d est une référence de la classe Date
qui pointe sur un objet alloué grâce à l’expression new Date(1981, 5, 1)
. L’objet est donc une date spécifique. La classe est la définition qui regroupe toutes les dates possibles.
En prenant l’analogie de la théorie des ensembles, Date
serait un ensemble et l’objet “1 mai 1981” une valeur de cet ensemble.
L’objet référencé par d a été instancié par un constructeur précédé par new
. Un constructeur porte le nom de la classe. Dans cet exemple, le constructeur prend en argument 3 int
s que sont l’année, le mois et le jour.
L’image ci-dessous résume le vocabulaire:
L’image ci-dessous illustre le monde des références (la pile) et le monde des objets (le tas). Gardez toujours cette image en tête, elle vous servira plus tard. Souvenez-vous que la pile est une structure organisée de manière continue. L’accès est très rapide (empiler, dépiler le premier élément). Le tas est un espace mémoire où l’accès est plus lent.
Regardons dans la documentation officielle de Java 14 quelles sont les fonctionnalités offertes et jouons un peu avec.
Fonctionnalités #
Une fonctionnalités est appelée méthode en POO. Il s’agit d’une fonction rattachée à un objet.
Comparaison de dates:
- before est une méthode qui prend en argument une autre date et retourne
true
si d est avant l’argument.
|
|
Extraction d’informations
- getMonth nous permet de récupérer le numéro du mois
|
|
Critiques #
Si nous lisons la documentation officielle, nous remarquons que cette classe offre très peu de fonctionnalités. Il est difficile de connaître quel était le jour de la semaine le 1er mai 1981. Il est impossible de déterminer à quelle date nous nous trouvions 50 jours avant ou encore si l’année 1981 était une année bissextile.
Mais il y a pire. Nous constations qu’il est possible de modifier une date! Pourquoi souhaiterions-nous changer une date ? Changer la date de naissance d’une personne est une chose. Dans un dossier patient, il doit être possible de supprimer une date pour en mettre une nouvelle. Mais une date. A quel moment nous souhaiterions dire “le premier mai 1990… ben finalement c’était un trois mai 1990” ? Tous les événements qui auraient eu lieu le 1er mai 1990 (qui aurait cette référence) verraient leur date reportée au 3 mai !
|
|
Toutes les occurrences qui référencent d ont maintenant un état indésirable.
Continuons:
|
|
Conclusions #
Nos dates sont mutables (c’est-à-dire que leur état interne peut changer) et ne devraient pas l’être. La classe offre peu de fonctionnalités intéressantes et nous autorise à réaliser des opérations douteuses.
Conseil
Evitez autant que possible des classes mutables. Il est difficile de leur faire confiance. Il est beaucoup plus facile à comprendre, isoler, tester, corriger ou paralléliser une classe qui ne l’est pas. Privilégiez, si possible, la conception et l’utilisation de classes immutables.(Réf.: Les principes de privilège)
Conseil
Vos objets doivent toujours se trouver dans un état cohérent et permettre des fonctionnalités permettant des transitions d’un état valide à un autre état valide.
Utilisation de LocalDate
#
Java 8 introduit une nouvelle API pour gérer les dates et s’excuse pour le désagrément. Le package java.time
contient plusieurs classes pour gérer les dates, les heures, les périodes ou les durées. Concentrons-nous sur LocalDate
. Cette dernière est immutable, il ne sera donc pas possible de changer son état. La documentation
nous offre beaucoup plus de fonctionnalités.
Remarquons d’abord que cette classe n’offre pas de constructeurs. Un motif proposé à la place est ce que l’on appelle une fabrique : construire l’objet à l’aide d’une méthode statique. Cette technique comporte l’avantage d’avoir un nom plus explicite.
|
|
Les méthodes statiques ne sont pas rattachées à un objet particulier, mais à un type.
Nous remarquons avec l’exemple ci-dessous que Java retourne une exception et nous interdit donc de nous trouver dans un état incohérent.
|
|
Regardons maintenant un extrait des méthodes d’instances (méthode rattachée à un objet instancié cette fois-ci).
|
|
Même si un objet est immutable, il est possible d’obtenir un nouvel état à partir d’un état initial:
|
|
L’objet est immutable mais la référence peut changer :
|
|
Etant donné que les méthodes retournent le nouvel état plutôt que de le modifier, il est possible de chaîner les appels:
|
|
Pour rendre une référence constante, il est nécessaire de précéder la déclaration par le mot-clé final
:
|
|
Quiz #
Réalisez une expression permettant, à partir d’un date, de vérifier si l’année dans laquelle elle se trouve est bissextile.
- Vous avez droit aux méthodes d’instances suivantes:
withDayOfMonth
,withMonth
,minusDays
,getDayOfMouth
. Consultez la documentation officielle.
Le cas du String
#
La classe String
est également une classe immutable qui est couramment utilisée pour manipuler des chaînes de caractères. La création se fait simplement, sans constructeur ni méthode grâce à la méthode de conversion implicite dite d’autoboxing.
|
|
Nous remarquons que l’objet est immutable mais que la référence s n’est pas constante. Il est donc possible de faire pointer s sur une nouvelle version du String
.
|
|
Ces deux instructions sont illustrées ci-dessous:
Exemples de méthodes d’instance #
String test = "Abcdef";
test.contains("bc"); // retourne true
test.contains("ab"); // retourne false
test.concat("ghi"); // retourne Abcdefghi mais ne modifie
// pas l'instance test
test.toUpperCase(); // retourne "ABCDEF"
test.split("c"); // retourne { "Ab", "def" }
Exemples de méthodes de classe #
String.join(" - ", "This", "course", "is", "awesome");
// ou
String.join(" - ", new String[]{"This","course","is","awesome"});
// retourne "This - course - is - awesome"
String.valueOf(22); // retourne "22"
Quiz #
Ecrivez, à l’aide du split
et du join
, une expression qui retourne une nouvelle chaîne de caractères dont tous les ;
sont remplacés par des ,
. Par ex: "33;44;55"
doit retourner "33,44,55"
.
Attention
L’égalité
==
se fait par valeur pour les types primitifs (les énumérations et singletons) mais par référence pour les objets. Utilisez toujours la méthodeequals
pour ces derniers.
Comparaison d’objet #
Il est important d’utiliser la méthode equals
pour comparer la structure d’un objet.
String s = "bonjour";
s.toUpperCase() == s.toUpperCase(); // ==> false (les réfs sont différentes)
s.toUpperCase().equals(s.toUpperCase()); // ==> true
Quiz #
Ecrivez une expression qui permet de préciser si une chaine de caractères est en majuscule.
Le cas du StringBuilder
#
Cette classe est la version mutable pour la manipulation d’une chaîne de caractères. Les objets immutables ont beaucoup de bénéfices au détriment de l’efficacité. Le Builder
est un patron de conception populaire qui permet de manipuler un objet mutable, permettant éventuellement qu’il soit incohérent, avant de le construire pour le transformer dans sa version immutable.
|
|
Le cas de List
#
Le type List
de manipuler des listes dont le nombre d’éléments peut varier contrairement aux tableaux statiques. Ce type est réellement une interface et non une classe. Une interface est un contrat que doit avoir une classe qui la respecte. Lors de la création d’une liste, il est donc important de préciser quelle implémentation est utilisée. Les listes sont génériques : elles prennent en paramètre le type d’éléments qui s’y trouve. La logique d’une liste est la même, quel que soit ce paramètre. Par exemple: retirer un élément, ajouter un élément, compter le nombre d’éléments,… sont des opérations qui sont identiques pour une liste de String
s, une liste d’entiers ou une liste de Schtroumpf
s.
Quelques précisions:
- Le paramètre peut être n’importe quel type non-primitif (une classe ou une interface)
List<int>
interditList<Integer>
ok
ArrayList
etLinkedList
sont des classes qui peuvent être utilisées
Exemple d’instanciation et ajout de trois éléments:
|
|
Observez ci-dessus, la référence greetings
est une constante (mot-clé final
), mais l’objet est quant à lui mutable. Il est impossible d’attribuer greetings
à une autre liste mais il est possible de modifier l’état de l’objet pointé.
Il est possible de créer une liste avec ses valeurs en une ligne.
L’exemple ci-dessous montre comment créer une liste en spécifiant directement les valeurs. L’exemple utilise également l’inférence du type avec le mot-clé var. Notez que ces constructions retournent des listes immutables. Il est donc impossible d’ajouter ou de supprimer un élément. Vous remarquerez que les débordements ne sont pas autorisés. Il est impossible d’accéder à un indice qui n’existe pas, contrairement au C
.
|
|
Les tableaux statiques #
L’étude de cas des tableaux statiques est de moindre intérêt pour la POO. Ils offrent peu de fonctionnalités et comportent plusieurs défauts. Privilégiez d’autres collections comme les List
s, Set
s, … Par contre, ils restent incontournables. Profitons de ce chapitre pour parcourir quelques exemples d’utilisation.
Un tableau statique possède une taille fixe. Il est possible de modifier son contenu, mais il est impossible de supprimer ou d’ajouter des éléments.
La syntaxe utilise la notation []
pour la déclaration et la modification.
Voici deux manières possibles de créer un tableau statique. En précisant les valeurs ou en déterminant la taille.
type[] tab1 = {a, b, c};
type[] tab2 = new type[TAILLE];
Attention, s’il s’agit d’un tableau d’objet, la valeur null
le compose. S’il s’agit de types primitifs, leur valeur “neutre” sera utilisée. Le mot-clé null
permet d’indiquer qu’une référence ne pointe sur aucun objet.
Danger
Le concept de la valeur arbitrairenull
pour signifier une absence de valeur a été inventé en 1964 par Tony Hoare. En 2009, il l’appelle son “erreur à plusieurs milliards” (The Billion Dollar Mistake) tant il a généré de bugs dans l’industrie. Son utilisation est fortement déconseillée.
Conseil
Veillez à ne jamais réaliser une API (classe, composant, module…) qui pourrait retourner une référencenull
ou l’accepter en argument. Si vous l’employez, faites en sorte de ne jamais l’exposer. Isolez son utilisation et réduisez sa visibilité. Traitez cenull
comme s’il s’agissait d’un virus : avec beaucoup de précautions.
Remarques
void et null ne sont pas des types
- void signifie qu’une fonction ne retourne pas de valeur, il s’agit généralement d’un effet de bord.
- null signifie qu’un objet n’est pas instancié
Exemples d’instanciation de tableaux
int[] is; // is = null
int[] empty = {}; // tableau vide
int[] fibonacci = {1, 1, 2, 3, 5, 8, 13};
Exemple d’instanciation de tableaux avec taille arbitraire
float[] fs = new float[4] // fs = {0.0, 0.0, 0.0, 0.0}
String[] fs2 = new String[4] // fs2 = {null, null, null, null]
Plusieurs dimensions possibles:
double[][] matrix = new double[2][3]; // deux lignes, trois colonnes
Copie et référencement:
double[][] matrix2 = matrix; // /!\ matrix et matrix2 pointent
// sur la même instance
double[][] matrix3 = matrix.clone() // copie le tableau
Un tableau expose uniquement l’attribut length
pour connaître le nombre d’éléments. Il ne comporte aucune méthode.
int[] is = new int[12];
is.length; // ==> 12
Plusieurs méthodes statiques de la classe utilitaire Arrays
permettent de manipuler un tableau.
int[] is = {4,7,8,9,2,9};
Arrays.sort(is); // trie le tableau, is = {2,4,7,8,9,9};
Arrays.fill(is, 22) // is = {22,22,22,22,22,22};
Arrays.equals(is, new int[]{4,7,8,9,2,9}); // true
Absence de valeurs #
Conseil
Pour représenter l’absence d’articles dans un sac de commissions, retournez un sac de commissions vide !De la même façon:
- Pour représenter l’absence de valeurs dans une liste, retournez une liste vide et non une référence null (par ex:
List.of()
ouCollections.emptyList()
)- Pour représenter l’absence de valeurs dans un tableau statique, retournez un tableau vide (par ex:
{}
)