Vous aimez ce que vous lisez sur ce blog ?
Envie d'aller plus loin avec véritable formation d'expertise en Java ?
Venez suivre ma formation Masterclasse Expertise Java !

"Même un développeur experimenté a besoin de continuer à apprendre. Et dans cette formation... j'ai appris beaucoup !" - A.G., Java Champion

Sessions intra-entreprises sur demande : contact[at]mokatech.net.
Inscrivez-vous vite !

Java Quiz #23

Saurez-vous analyser ce code sans le copier/coller dans un IDE ?

  1. /** Gestionnaire de configuration. Immutable. */
  2. public class SettingsManager implements Serializable {
  3.  
  4. /** Ensemble des settings, classés par leur nom */
  5. private final Map<String, ? extends Serializable> settings = Collections.EMPTY_MAP;
  6.  
  7. /** Constructeur */
  8. public SettingsManager(Map<String, ? extends Serializable> settings)
  9. { this.settings = settings;
  10. }
  11.  
  12. /** Permet de récupérer un Setting selon son nom. Peut renvoyer null. */
  13. public Serializable getSetting(String settingName)
  14. { return this.settings.get(settingName);
  15. }
  16. }

Réponse :
Il fallait, dans ce quiz, trouver une solution en deux étapes : premièrement, faire compiler le code, et ensuite, le rendre conforme aux specifications.

En l'état, le code ne compile pas car la Map "settings", déclarée final, est initialisée deux fois : une fois lors de sa déclaration, et une seconde fois par le constructeur.
Une solution possible consiste à réaliser toute l'initialisation au sein du constructeur :

  1. private final Map<String, ? extends Serializable> settings; // Suppression de l'initialisation
  2. public SettingsManager(Map<String, ? extends Serializable> settings)
  3. { this.settings = (settings!=null) ? settings : Collections.EMPTY_MAP;
  4. }

Ensuite, deux problématiques :

  1. La sérialisation
  2. L'immutabilité

Pour la sérialisation, comme fabien29200 l'a fait remarquer, l'interface Map ne donne aucune garantie concernant la sérialisation; seules certaines de ses implémentations implémentent Serializable (notamment celles appartenant au package java.util).
Une solution consisterait donc à changer le type de la propriété "settings" et à la remplacer par une implémentation spécifique, comme HashMap. Il serait alors nécessaire de transférer les éléments de la map passée en paramètre du constructeur dans notre Map sérialisable :

  1. private final HashMap<String, ? extends Serializable> settings;
  2. public SettingsManager(Map<String, ? extends Serializable> settings)
  3. { this.settings = (settings!=null) ? settings : new HashMap(settings);
  4. }

Les objets placés dans la Map étant eux-même sérialisables d'après le paramétrage, ce problème est résolu.

Pour l'immutabilité, en revanche, nous nous heurtons à un problème.
Il est facile de rendre la Map elle-même immutable, grâce au décorateur fourni par la classe factory Collections :

  1. private final Map<String, ? extends Serializable> settings;
  2. public SettingsManager(Map<String, ? extends Serializable> settings)
  3. { this.settings = (settings!=null) ? settings : Collections.unmodifiableMap(new HashMap(settings));
  4. }

Notons au passage que la "sérialisabilité" de l'ensemble est conservée, selon la Javadoc :

The returned map will be serializable if the specified map is serializable.

Le véritable problème vient du fait que nous n'avons aucun moyen de forcer les objets placés dans la Map à être eux-mêmes immutables. La seule solution consiste ici à documenter proprement cette condition/limitation, et à espérer que les développeurs utilisant notre classe s'y conformeront...


Commentaires

1. Le vendredi 21 novembre 2008, 17:56 par Tom

settings est final donc on ne peut l'affecter dans le constructeur. Et collections.empty_map est immutable... autant le laisser à null et enlever le final. Puis dans le constructeur, faire un : this.settings = Collections.unmodifiableMap(settings);

Tom

2. Le vendredi 21 novembre 2008, 18:47 par Olivier Croisier

Le modificateur "final" n'interdit pas l'affectation d'une valeur dans le constructeur, au contraire : il faut que la variable "final" ait été initialisée avant la fin de l'exécution du constructeur, que ce soit via une affectation lors de sa déclaration ou dans le constructeur lui-même. Ce n'est donc pas (tout à fait) le problème soulevé par ce quiz.

En revanche, tu as bien remarqué qu'il fallait rendre la map passée en paramètre immuable : sans cela, la classe SettingsManager serait vulnérable aux modifications de la map sous-jacente.

3. Le dimanche 23 novembre 2008, 05:21 par François-Xavier

mais alors si on rend la Map immuable, ça servirait à stocker l'état de la Map, avant des opérations "à risque" ?

François-Xavier

4. Le lundi 24 novembre 2008, 16:52 par fabien29200

Est-ce le fait que la classe soit "Serializable" ?
Car on n'est pas certain que l'implémentation de la Map soit serializable.
Or dans la Javadoc, il est écrit : "During deserialization, the fields of non-serializable classes will be initialized using the public or protected no-arg constructor of the class. "
Et notre classe n'a pas de constructeur sans argument.
PS: je n'ai pas copié / collé le code dans un IDE.

Ajouter un commentaire

Le code HTML est affiché comme du texte et les adresses web sont automatiquement transformées.