Cours 3 : Création de classes
Viviane Pons
Master BIBS Université Paris-Saclay
Comment créer nos propres classes ?
Dans ce cours, on va voir les étapes de création d’une classe en suivant étape par étape l’exemple suivant.
On a vu que les classes de l’API Java étaient organisées en package. En fait le vrai nom de la classe Scanner
est :
java.util.Scanner
La classe Scanner
appartient au package java.util
. En effectuant un import
on s’autorise simplement à oublier la partie du nom qui relève du package.
Il pourrait tout à fait exister une autre classe appelée Scanner
dans un autre package : l’utilisation des packages évite les conflits de noms.
com.myorg.Scanner // exemple factice
Jusqu’à présent, nous avons toujours créé une unique classe (contenant le main) dans le “default package” (pas de package). On va arrêter !
Traditionnellement, le nom du package reflète la provenance de la classe (par exemple, avec le nom de l’entreprise) et ressemble à une url à l’envers. On imagine qu’on crée nos classes de façon unique dans le grand univers qui contient toutes les classes Java.
Pour nos classes à nous, on utilisera le nom de base suivant :
fr.upsaclay.bibs
Que l’on déclinera ensuite en fonction des packages créés.
fr.upsaclay.bibs.monpackagebla
fr.upsaclay.bibs.encoreunpackage
Création du package
fr.upsaclay.bibs.rational
avec IntelliJ.
On remarque que la structure de package crée l’équivalent en arborescence de fichier.
Dans le dossier du package correspondant, on va créé un fichier par classe
MaSuperJolieClasse
Démo : création des classes RationalExample
dans fr.upsaclay.bibs
et Rational
dans fr.upsaclay.bibs.rational
.
La présence (ou non) du modificateur public
avant le mot class
indique la visibilité de la classe :
public
: la classe est visible (utilisable) par les classes en dehors du packageIl existe d’autres modificateurs que l’on peut rajouter en plus de public
avant le mot class
: abstract
et final
, nous verrons leur signification quand nous traiterons de la question de l’héritage.
Dans notre exemple, on a 2 classes : La classe Rational
pour créer les objets de type “nombres rationnels”, la classe RationalExample
qui ne contiendra qu’une méthode main
et nous servira pour tester des exemples.
package fr.upsaclay.bibs;
import fr.upsaclay.bibs.rational.Rational;
public class RationalExample {
public static void main(String[] args) {
Rational r;
}
}
Rational
Dans la classe Rational
:
Qu’on utilise dans RationalExample
:
Rational r = new Rational(1,2);
Visibilité (des champs et des méthodes) :
public
tout le monde peut voir le champ (et le modifier !)protected
: les éléments du package ainsi que les classes héritées ont accès au champprivate
: seule la classe a accès au champfinal
pour un champ ?Le champ est fixé à la création de l’objet et ne pourra plus être modifié
Le mot clé a aussi une signification pour les méthodes que l’on verra plus tard.
public / protected / private
s’applique que pour les champsQuel est son rôle ? Initialiser les champs (en particulier, les champs de type final
, et effectuer les vérifications / calculs initiaux nécessaires au fonctionnement de l’objet créé)
On voudrait vérifier le dépassement de capacité. On va définir la capacité de nos rationnels à l’aide de champs statiques : spécifiques à la classe et non à ses instances (objets).
On adapte notre constructeur :
/**
* Adds rationals
* @param r2 another rational object
* @return the Rational representing the sum of object and `r2`
*/
public Rational add(Rational r2) {
return new Rational((long)n*r2.d + (long)r2.n * d, (long)d * r2.d);
}
Je peux écrire directement n
et d
dans ma méthode : la méthode est liée à l’objet créé, ce sont donc les champs de l’objet.
{ ... }
(classe, méthode, bloc if
/ for
)this.champ
Exemple d’utilisation de la méthode add
Rational r = new Rational(1,2);
Rational r2 = new Rational(1,6);
Rational r3 = r.add(r2);
System.out.println(r3.n);
System.out.println(r3.d);
Affiche 8 et 12. Où et comment simplifier la fraction ?
Une méthode qui ne dépend pas de chaque objet (comme add
) mais qui est comme une “fonction globale” attachée à la classe. On ne peut pas accéder aux champs de l’objet depuis une méthode statique !
Rappel : ici protected
signifie que la méthode est visible depuis le package ou les classes héritées.
public Rational(long n, long d) {
if(d == 0) {
throw new IllegalArgumentException("division by 0");
}
long div = gcd(n,d);
n = n / div;
d = d / div;
if(n > max_operand || n < min_operand || d > max_operand) {
throw new IllegalArgumentException("Exceeds operand capacity");
}
this.n = (int)n;
this.d = (int)d;
}
En Java, il est possible de définir plusieurs fonctions portant le même nom si les paramètres (nombre / type) sont différents.
protected Rational(long n, long d, boolean simplify) {
...
}
public Rational(long n, long d) {
this(n, d, true);
}
public Rational(long n) {
this(n,1, false);
}
public Rational() {
this(0);
}
L’appel à un autre constructeur de la classe se fait par this(...)
et doit toujours être fait en premier.
On ajoute quelques méthodes de calcul à notre objet : multiply
, minus
, doubleValue
/**
* Return whether r2 is equal to object as rationals
* @param r2 a rational
* @return true if this is equal to self
*/
public boolean equals(Rational r2) {
return (r2.n == n && r2.d == d);
}
@Override
public boolean equals(Object obj) {
if(obj == null) {
return false;
}
if(!Rational.class.isAssignableFrom(obj.getClass())) {
return false;
}
return this.equals((Rational)obj);
}
La valeur de hachage, ou “hash code” est une clé associée à l’objet.
Cette valeur est utilisée par les structures de données type Set
et Map
.
L’annotation @Overide
est utilisée pour indiquer au compilateur que la méthode redéfinit une méthode définie précédemment (voir le prochain chapitre sur l’héritage). Les méthodes toString
equals
et hashCode
sont héritées de la classe Object
et utilisent l’adresse mémoire par défaut.
!! Quand on redéfinit equals
on doit redéfinir hashCode
!!
Pourquoi faire ?
Pour utiliser des fonctionalités génériques. Par exemple, car je voudrais trier des listes de rationnels.
/**
* Value of given string as Rational
* @param s a String value
* @return
*/
public static Rational valueOf(String s) {
String[] parts = s.split("/");
if (parts.length == 1) {
return Rational.valueOf(Integer.valueOf(s));
} else if (parts.length == 2) {
return new Rational(Integer.valueOf(parts[0]), Integer.valueOf(parts[1]));
} else {
throw new NumberFormatException("For input string: \"" + s + "\"");
}
}
Pour l’instant, on a testé notre programme en faisant des essais dans la fonction main
. Mais “dans la vraie vie”, la fonction main
sert à écrire le coeur de l’application : toutes les fonctionnalités et les cas de figure ne sont pas appelés.
On veut créer des tests de façon séparée, indépendants d’une application particulière.
Un test unitaire est une procédure permettant de tester le bon fonctionnement une partie spécifique d’un programme.
JUnit est un “framework” (un “cadre”, une “infrastructure”, un “schéma”) permettant de réaliser simplement des tests unitaire en Java.
On crée un package
fr.upsaclay.bibs.rational.test
Dans ce package on crée une classe RationalTest
où on importe les classes suivantes :
import static org.junit.jupiter.api.Assertions.*;
import org.junit.jupiter.api.Test;
import fr.upsaclay.bibs.rational.Rational;
class RationalTest {
Le mot junit
apparait en rouge dans IntelliJ : il faut ajouter JUnit 5 au projet.
On crée des méthodes de test
On lance les tests depuis IntelliJ
Junit nous indique :
Pour faire les tests, on utilise les méthodes statiques de la classe Assertions
assertEquals
assertNotEquals
assertThrows
Typiquement, on crée une méthode de test par méthode de la classe testée et on teste … tout ce à quoi on pense.
Les tests unitaires ne pourront jamais vous assurer que votre programme fonctionne. Ils peuvent être complétés par des tests fonctionnels ou de la vérification formelle.
Cependant, ils permettent de détecter de nombreuses erreurs et facilitent la maintenance du code.
Nous avons vu les concepts objets illustrés avec le Java. Tout ça est aussi possible en python avec quelques différences, voyez l’exemple de la classe Rational en python