novembre
2008
Aujourd’hui j’inaugure un genre nouveau avec de petites exercices en rapport avec Java, dans l’objectif de mieux comprendre les rouages et les particularités du langage.
Nous allons donc voir qu’avec une mauvaise conception d’une classe toute simple, il est possible de « casser » le principe encapsulation, si chère à la POO, et qui permet à une instance de classe de protéger ses attributs d’éventuelles modifications externes…
Prenons donc la classe suivante dont le code complet se trouve ci-dessous :
import java.util.Calendar;
import java.util.Date;
public final class MyObject {
private final Date date;
private final Number number;
public MyObject(Date date, Number number) {
// On vérifie que la date soit plus récente que le 1 janvier 2000 :
Calendar cal = Calendar.getInstance();
cal.set(2000, java.util.Calendar.JANUARY, 1);
if (cal.before(date)) {
throw new IllegalArgumentException("'date' doit être supérieur à l'an 2000");
}
this.date = date;
// On vérifie que l'objet Number soit bien un nombre positif
if (number.intValue() < 0) {
throw new IllegalArgumentException("'number' doit correspondre à un nombre positif !");
}
this.number = number;
}
public void hello() {
System.out.println("Date : " + this.date);
System.out.println("Number : " + this.number.intValue());
System.out.println();
}
}
Il s’agit donc d’une classe finale (qui ne peut donc pas être redéfinie), qui dispose de deux attributs à visibilités privés. Ces deux attributs sont également finaux (ils ne peuvent pas être réassignés par la suite), et leurs valeurs sont vérifiées lors de la construction de l’objet, selon les règles suivantes :
- Le premier attribut, de type
java.util.Date
, doit correspondre à une date venant après le 1er janvier 2000. - Le second attribut, de type
java.lang.Number
, doit correspondre à un nombre avec une valeur entière positive.
Bien entendu, la classe ne possède pas de méthode permettant de modifier ses attributs, mais simplement une méthode hello()
qui permet d’afficher la valeur de ces attributs.
Enfin, le code s’exécutera dans un environnement protégé par un SecurityManager, ce qui fait qu’on ne pourra utiliser la méthode « barbare » setAccessible(true) de l’API de Réflection pour enfreindre les règles d’encapsulations.
L’objectif sera donc de modifier la valeur des attributs de la classe MyObject et de leurs donner des valeurs incorrects, le tout sans toucher au code de cette dernière.
L’exercice se basera sur le code suivant :
public class Main {
// La méthode main() ne doit pas être modifié !
public static void main(String[] args) throws Exception {
// On défini un SecurityManager :
System.setSecurityManager(new SecurityManager());
// Et on appelle la méthode exercice() :
exercice();
}
// Exemple de base :
public static void exercice() {
Date date = new Date(); // Date actuelle
Number number = new Integer(100); // 100
MyObject obj = new MyObject(date, number);
obj.hello();
obj.hello();
}
}
Donc les questions sont :
- En modifiant uniquement le code de la méthode
exercice()
, comment faire en sorte que les deux appels àobj.hello()
affichent des valeurs différentes pour la même instance de l’objet ?
On pourra pour cela utiliser toutes les classes et méthodes de l’API standard, et créer autant de nouvelles classes ou méthodes que neccessaire.L’idéal serait d’obtenir un résultat de la forme suivante :
Date : Mon Nov 03 17:19:08 CET 2008
Number : 100
Date : Thu Jan 01 01:00:00 CET 1970
Number : -100 - Existe-t-il dans l’API standard des classes qui ne posent pas ce genre de problème lorsqu’elles sont utilisées comme attribut ?
- Comment corriger la classe MyObject pour corriger ce problème ?
Alors ? Avez-vous trouvé l’origine du problème ?
Note 1 : Il faut bien sûr appeler la méthode hello()
de la classe MyObject
!
Note 2 : La date du premier janvier 1970 correspond tout simplement à un new Date(0L)
Note 3 : La solution pour la date est la plus évidente, mais dans les deux cas la solution est très proche.
14 Commentaires + Ajouter un commentaire
Tutoriels
Discussions
- [ fuite ] memoire
- Définition exacte de @Override
- [REFLEXION] Connaitre toutes les classes qui implémentent une interface
- Classes, méthodes private
- Difference de performances Unix/Windows d'un programme?
- Possibilité d'accéder au type générique en runtime
- Recuperation du nom des parametres
- jre 1.5, tomcat 6.0 et multi processeurs
- L'apparition du mot-clé const est-il prévu dans une version à venir du JDK?
C’est édité
a++
En fait, il manque juste entre les deux exemples un texte « on devrait avoir celui-ci » qui ferait le pendant à « Par exemple au lieu d’avoir ceci » car suite à ta réponse, j’ai relu le début et c’est super limpide.
benwit > Non tu as raison : la seconde méthode correspond au code correct puisqu’on n’accède qu’une seule fois aux données. C’est ce que je voulais dire je me suis mal exprimé
Sinon pour de nouveaux jeux il faudrait que j’y penses, mais je recommence petit à petit à blogguer un peu
a++
A propos de ton dernier post dans ce billet,
Dans le premier exemple, d’accord puisque tu appelles deux fois de suite la méthode du Number dont tu ne connais pas l’implémentation (RandomNumber par exemple)
Mais dans le deuxième, tu n’appelles qu’une fois la méthode du Number dont tu ne connais pas l’implémentation donc ça ne devrait pas causer de soucis ?
Si dans cette deuxième méthode, tu avais enlèver le « this » dans le test, je suis de nouveau d’accord, ça peut poser problème.
Quelque chose m’aurait échappé ?
A quand les nouveaux jeux ? ;o)
Pour compléter la réponse de Hikage : lorsqu’on récupère un type non-immuable, on doit d’abord faire une copie AVANT de vérifier la valeur, puis de vérifier la valeur de cette copie.
Par exemple au lieu d’avoir ceci :
if (number.intValue() < 0) { <br />
throw new IllegalArgumentException("'number' doit correspondre à un nombre positif !"); <br />
} <br />
this.number = new Integer(number.intValue()); <br />
on devrait avoir celui-ci :
this.number = new Integer(number.intValue()); <br />
if (this.number.intValue() < 0) { <br />
throw new IllegalArgumentException("'number' doit correspondre à un nombre positif !"); <br />
} <br />
Car pour une classe muable, rien ne garantie que deux appels successif de la même méthode renverront bien le même résultat.
En effet on pourrait avoir par exemple une implémentation comme celle-ci :
class RandomNumber extends Number { <br />
@Override <br />
public int intValue() { <br />
// Nombre aléatoire entre -5000 et 5000 <br />
return (int) ( (Math.random() * 10000) - 5000 ); <br />
} <br />
<br />
// ... <br />
}
En clair si on veut protéger ses attributs, on ne devrait jamais faire confiance aux classes muables
a++
c’est sur que String immutable, ça aide bien
a chaque fois que je passe findbugs sur un projet, j’ai le dilemme…
mais effectivement, je suis d’accord avec toi djo.mos, si je devais designer une api, je serai plus attentif a ca.
Lunatix > Là est toute la question
S’il y a peu de bugs de ce genre, c’est surtout pour deux raisons :
Si la classe String n’était pas immuable, on rencontrerait ce genre de problème plus souvent
a++
lunatix> pareil pour moi.
Mais je crois que ça ne s’applique pas à ceux qui développent des applications prêts à porter si on peut dire, plutôt aux développeurs d’APIs qui seront utilisés par d’autres programmeurs …
et au fait ? vous pratiquez la protection via copie des attributs mutables dans vos projets ? j’avoue que j’hésite souvent, j’ai finalement rarement vu de bugs provoqués a cause d’une modification intempestive de variable.
1. Oui c’est tout à fait cela. Dès lors qu’on utilise un type non-immuable comme attribut, on doit s’attendre à ce qu’il puisse être modifié depuis l’extérieur de la classe.
Le code de la méthode exercice() pourrait donc ressembler à cela :
Date date = new Date(); // Date actuelle <br />
AtomicInteger number = new AtomicInteger(100); // 100 <br />
<br />
MyObject obj = new MyObject(date, number); <br />
<br />
obj.hello(); <br />
<br />
date.setTime(0L); // 1er janvier 1970 <br />
number.set(-100); <br />
<br />
obj.hello(); <br />
}
2. Plus précisément que les classes filles de Number, il faut utiliser des types immuables, c’est à dire dont la valeur ne varie jamais au cours de l’exécution. Par exemple : Integer, Double, String,…
Pour rappel la valeur d’un type immuable est déterminé lors de sa création, et ne peut pas être modifié par la suite…
3. C’est presque cela… puisqu’avec ce code il est encore possible de passer outre les vérifications du constructeur (c’est un peu tordu mais c’est possible).
a++
1. Concernant la Date, cette classe n’est pas immuable et peut etre modifier via setTime.
Concernant le champ Number, il suffit de sous classer la classe Number pour en faire une classe non immuable. Il sera donc possible de modifier le comportement de la méthode intValue();
2. Si l’on utilise les classes filles de Number, il n’y a pas ce problème. Celle-ci sont finales et immuables. Donc impossible de changer le comportement de celles-ci sans utiliser l’API Reflection.
3. Concernant la manière de corriger MyObject, il suffira de ne pas stocké directement la référence passée dans le constructeur mais de faire une copie :
Number, ce n’est pas trop contraignant comme contrat..
Tiens, on ne peut pas utiliser de balises XHTML dans une balise
.
Ah ben c’est logique en fait
Voilà déjà la solution pour le champ de date:
obj.hello(); <br />
<br />
<b>date.setTime(0L);</b> <br />
<br />
obj.hello(); <br />