1 La classe Object

publicité
Université Claude Bernard Lyon 1
ISFA
MASTER 2 – IR, 2015–2016
Remise à Niveau Java
TD4. Classe Object et comparaison d’objets en Java
André FABBRI
Compétences indispensables à acquérir :
• Faire connaissance avec la classe Object
• Distinguer l’égalité superficielle de celle profonde entre deux objets
1
La classe Object
Dans l’API Java, toute classe existante ainsi que toute classe que vous créez hérite automatiquement de la classe Object. Ainsi n’importe quel objet créé a aussi
accès aux méthodes proposés par cette classe. Ces méthodes correspondent à des fonctionnalités fondamentales de Java communes à tous les objets. Vous trouverez
ci-dessous les signatures des principales méthodes de Object habituellement utilisées :
• public String toString() : représentation de l’objet sous la forme d’un
String ;
• public boolean equals(Object obj) : vérifie si l’objet sollicité est égal à l’objet obj ;
• public int hashcode() : retourne une valeur de hashage pour cet objet ;
• protected Object clone() throws CloneNotSupportedException :
retourne une copie de l’objet.
NB : throws CloneNotSupportedException signifie que la méthode clone est suceptible de retourner une exception Java. Nous n’aurons pas le temps de couvrir
cette fonctionnalité de Java mais pour plus d’information veuillez vous reporter à :
http: // jmdoudoux. developpez. com/ cours/ developpons/ java/ chap-exceptions. php
1.1
Des méthodes communes à tout objet
Si les méthodes de Object sont communes à toutes les instances en Java, leur comportement par défaut est très simple, voire simpliste :
• public String toString() : retourne l’adresse mémoire de l’objet ;
• public boolean equals(Object obj) : compare les adresses mémoires
des deux objets.
Pour mieux saisir le comportement de ces méthodes en particulier, il est nécessaire de
bien comprendre le fonctionnement interne de Java.
1
(rappel) - initialisation et affectation en Java
En Java, il est important de distinguer une initialisation d’une affection pour
les instances d’objets a . Une initialisation (opérateur new) réserve un nouvel espace
mémoire pour l’objet. L’espace mémoire d’un objet inclut une zone pour chacun ses
attributs ainsi qu’une adresse mémoire unique.
Lors d’une affectation (opérateur = tout seul), aucune zone mémoire n’est réservée.
La variable pointe simplement vers l’adresse mémoire qui lui a été affectée.
a. c.a.d qui ne correpsondent donc pas à des types primitifs (cf. TD2)
1.2
Exercice - mise en pratique
L’administration de l’ISFA souhaite réaliser une base de donnée des anciens étudiants.
Pour chaque étudiant, on souhaite conserver les informations minimales pour les identifier (nom, prénom date de naissance) ainsi que l’année de leur première inscription,
la formation initialement suivie et le dernier diplôme qu’ils détenaient à cette époque
là. Cependant la procédure d’inscription génère chaque année une nouvelle fiche par
étudiant inscrit ; risquant ainsi d’introduire des doublons dans la base de données...
Dans un nouveau projet Netbeans, vous allez créer une classe Etudiant avec les attributs privés et les méthodes publiques suivant :
• nom : le nom de l’étudiant en String ;
• prenom : le prénom de l’étudiant en String ;
• naissance : la date de naissance de l’étudiant de type GregorianCalendar ;
• anneeInscription : année d’inscription à une formation de type int ;
• formation : nom de la formation suivie en String ;
• dernierDiplome : nom du dernier diplôme obtenu en String ;
• get... () : différents accesseurs à chacun des attributs privés ;
• setDernierDiplome(...) : modificateur pour l’attribut privé dernierDiplome ;
• Etudiant(...) : constructeur initisalisant l’ensemble des attributs.
Dans votre programme principal vous initialisez trois variables de type Etudiant comme
cela est présenté ci-dessous :
Etudiant e1 = new Etudiant(...); //intialisation d’un etudiant e1
Etudiant e2 = e1; //affectation de e1 dans e2
//initialisation de e3 avec les memes parametres que e1
Etudiant e3 = new Etudiant(...);
2
Variables codées en Java
Représentation dans l’espace mémoire
adresse mémoire
e1 : Etudiant
Opérateur =
@7dd66aa :Etudiant
Attribut "Nom"
@6ee5a6: String
Attribut "Prenom"
@a1378b: String
Attribut "Naissance"
@41ff21: GregorianCalendar
...
e2 : Etudiant
e3 : Etudiant
Opérateur new
@7d4b221 :Etudiant
Figure 1 – Illustration simplifiée de la représentation mémoire en Java
Question 1 Observez le résultat de la méthode toString() pour chacune des variables
de type Etudiant. Comment est structuré l’affichage par défaut ?
Question 2 Observez le résultat des appels suivant e1.equals(e2) et e1.equals(e3).
Les résultats obtenus vous paraissent-ils logiques ? Pourquoi ?
2
Redéfinition de la méthode toString
Les méthodes présentes dans la classes Object sont souvent utilisées par des composantes de l’API. La redéfinition de ces méthodes permet d’adapter leur
comportement aux objets des classes que l’on développe. Pour savoir comment redéfinir une fonction existante dans la classe mère, merci de vous référez au TD1.
En particulier, la méthode String toString() est automatiquement appelée lorsque
l’objet manipulé doit être converti en une variable de type String. C’est pour
cette raison que vous pouvez fournir directement un objet quelconque à la méthode
System.out.println(...).
Dans la classe Etudiant développée précédemment, redéfinissez la méthode String
toString() de la classe Object afin d’afficher le contenu des attributs de chaque objet.
Question 1 Affichez les attributs des objets e1, e2 et e3 dans le terminal à partir de
la méthode toString.
Modifiez à présent le dernier diplôme obtenu de l’instance e2 pour un diplôme différent
de celui initialement fourni aux instances e1 et e3.
Question 2 Affichez les attributs des objets e1, e2 et e3 dans le terminal à partir de
3
la méthode toString. Les résultats obtenus vous semblent-ils cohérents ? Pourquoi ?
3
Redéfinition de la méthode equals
Comme nous l’avons évoqué précédemment, la méthode equals par défaut compare
uniquement l’adresse en mémoire des différentes instances : on parle de
comparaison superficielle des instances (shallow comparison en anglais). Il s’agit
d’une relation d’égalité a minima dans la mesure où l’API ne dispose pas de plus amples
informations sur l’objet que vous souhaitez concevoir : deux variables partageant le
même espace mémoire sont identiques par défaut mais, à l’inverse, deux objets avec les
mêmes attributs peuvent avoir des espaces mémoires différents.
Une comparaison profonde (deep comparison en anglais) considère en revanche
les valeurs de chacun des attributs. Suivant l’application ou le modèle de données
choisi, il ne sera pas nécessaire de comparer la totalité des attributs des instances deux
à deux : c’est dans ce genre de situation que la méthode equals prend tout son sens !
Bonnes pratiques : redéfinition de boolean equals(Object obj)
Nous vous présentons ici les principales étapes à réaliser lorsque l’on redéfinit la méthode
equals. Les éléments présentés sont une synthèse rapide du très bon tutoriel accessible
au lien suivant : http://technofundo.com/tech/java/equalhash.html. Pour de plus
amples détails merci de vous référez à cette page.
1. Test de réflexivité : On vérifie tout d’abord que l’Object comparé ne correspond pas à la même adresse mémoire que l’objet courant (this), auquel cas on
retourne immédiatement true ;
2. Test du type : On vérifie ensuite que l’objet comparé est bien initialisé (différent
de null) et appartient à la même classe que l’objet courant (.getClass()). Le
cas échéant, il suffit de renvoyer false ;
3. Test des attributs : Une fois la nature de la classe vérifiée, on peut convertir
l’Object fournit en paramètre dans son véritable type (cast avec la notation
(MaClasse)). On compare enfin les attributs que l’on souhaite contrôler. On
fera néanmoins attention aux deux points suivants pour comparer les attributs
qui ne sont pas des types primitifs :
• vérifier qu’ils soient bien initialisés avant de les comparer ;
• toujours utiliser la méthode equals pour les comparer !
NB : Lorsque l’on redéfinit la méthode equals, il est en général impératif de redéfinir la méthode
hashCode comme nous le verrons par la suite.
Redéfinissez à présent la méthode equals pour la classe Etudiant. Nous considérerons
ici que deux étudiants sont identiques dès lors qu’ils disposent des attributs nom,prenom
et naissance identiques.
4
Créez à présent une quatrième instance e4 de la classe Etudiant initialisée avec les
mêmes paramètres que e3 à la différence près du prenom qui sera différent.
Question 1 Comparez les résultats par les appels des méthodes e1.equals(e2),
e1.equals(e2), e1.equals(e3),e1.equals(e4) et enfin e3.equals(e4). Vérifiez
que votre code s’exécute correctement.
4
Redéfinition de la méthode hashCode
La méthode int hashCode() est utilisée pour calculer une valeur dite de « hash »
ou « hashage » associée à une instance. Cette valeur est généralement utilisée pour
l’identifier « de façon unique » dans une table de hash.
Selon les spécifications de l’API Java, la méthode hashCode est tenue de retourner
la même valeur de hash lorsque deux objets sont considérés comme identiques, conformément à la méthode equals. C’est pourquoi lorsque l’on redéfinit
il est aussi nécessaire de redéfinir la méthode hashCode afin d’assurer le respect de cette
propriété. En pratique, il suffit d’utiliser dans le calcul de la valeur de hash, les mêmes
attributs que ceux comparés dans la méthode equals.
Bonnes pratiques : redéfinition de int hashCode()
Nous vous présentons ici les principales étapes à réaliser lorsque l’on redéfinit la méthode
hashCode. Les éléments présentés sont une synthèse rapide du très bon tutoriel accessible au lien suivant : http://technofundo.com/tech/java/equalhash.html. Pour
de plus amples détails merci de vous référez à cette page.
1. Initialisation : attribuer une valeur int initiale au hash de l’objet. Il s’agit en
général d’une valeur arbitraire (par exemple 7) ;
2. Hash des attributs : calculer une valeur de hash pour chacun des attributs en
int (32 bits) :
• pour un type primitif : on le convertira éventuellement int ;
• pour un type objet : on utilisera la fonction hashCode de l’objet ou une valeur
de 0, lorsque l’objet n’a pas été initialisé (égal à null).
3. Intégration des hash : on ajoutera progressivement à la valeur de hash de
l’objet les valeurs de hash de chacun des attributs en multipliant, avant ou après
chaque ajout, la valeur obtenue par un autre entier arbitraire (31 par exemple).
L’extrait de code ci-dessous illustre les pratiques présentées :
int hash = 7;
// a repeter pour chaque attribut
hash = hash * 31 + hashDeLAttribut ;
return hash;
5
Question 1 Redéfinissez à présent la méthode hashCode de la classe Etudiant afin
de respecter les spécifications de l’API Java. Vous vérifierez que des étudiants identiques
selon equals retournent bien la même valeur de hash.
Dans le programme principale instanciez à présent un objet de type HashSet<Etudiant>
correspondant à la base des anciens élèves (cf. http://docs.oracle.com/javase/7/
docs/api/java/util/HashSet.html pour de plus amples détails) . Vous ajouterez ensuite les étudiants correspondant aux deux promotions ci-dessous.
Question 2 Vérifiez que chaque étudiant est présent en un seul exemplaire et que les
dates indiquées correpsondent à celles des premières inscriptions (vous passerez en revue
les éléments du conteneur au moyen d’itérateur cf. TD2). Comment expliquez-vous un
tel résultat ?
// exemple de promotions pour tester votre programme
ArrayList<Etudiant> promo2013 = new ArrayList<Etudiant>();
promo2013.add(new Etudiant("Nuzit","Lucie",new GregorianCalendar(1990,06,14)
,2013,"L3-SAF","Baccalauréat"));
promo2013.add(new Etudiant("Tété","Hervé",new GregorianCalendar(1989,10,5)
,2013,"L3-SAF","Baccalauréat"));
promo2013.add(new Etudiant("Tinmarre","Augustin",new GregorianCalendar
(1990,02,20),2013,"L3-SAF","Deug-Info"));
ArrayList<Etudiant> promo2014 = new ArrayList<Etudiant>();
promo2014.add(new Etudiant("Nuzit","Lucie",new GregorianCalendar(1990,06,14)
,2014,"M1-IR","L3-SAF"));
promo2014.add(new Etudiant("Tinmarre","Augustin",new GregorianCalendar
(1990,02,20),2014,"M1-IR","L3-SAF"));
promo2014.add(new Etudiant("Detriage","Edgard",new GregorianCalendar
(1989,12,21),2014,"M1-IR","L3-Info"));
6
Téléchargement