web-dev-qa-db-fra.com

Est-il préférable de renvoyer une carte Immutable ou une carte?

Disons que j'écris une méthode qui devrait retourner un Map . Par exemple:

public Map<String, Integer> foo() {
  return new HashMap<String, Integer>();
}

Après avoir réfléchi pendant un moment, j'ai décidé qu'il n'y avait aucune raison de modifier cette carte une fois qu'elle a été créée. Ainsi, je voudrais retourner un ImmutableMap .

public Map<String, Integer> foo() {
  return ImmutableMap.of();
}

Devrais-je laisser le type de retour sous forme de carte générique ou dois-je spécifier que je retourne une carte Immutable?

D'un côté, c'est précisément pourquoi les interfaces ont été créées pour; pour masquer les détails de la mise en œuvre.
Par contre, si je le laisse comme ça, d’autres développeurs risquent de rater le fait que cet objet est immuable. Ainsi, je n'atteindrai pas un objectif majeur d'objet immuable; pour rendre le code plus clair en minimisant le nombre d'objets pouvant changer. Pire encore, après un certain temps, quelqu'un pourrait essayer de changer cet objet, ce qui provoquerait une erreur d'exécution (le compilateur n'en avertira pas).

88
AMT
  • Si vous écrivez une API publique et que l'immutabilité est un aspect important de votre conception, je la rendrais certainement explicite, en indiquant clairement le nom de la méthode indiquant que la carte renvoyée sera immuable ou en renvoyant le type concret de la carte. La mentionner dans le javadoc n'est pas suffisant à mon avis.

    Puisque vous utilisez apparemment l'implémentation de goyave, j'ai consulté la documentation et il s'agit d'une classe abstraite, ce qui vous donne un peu de flexibilité sur le type réel et concret.

  • Si vous écrivez un outil/une bibliothèque interne, il devient beaucoup plus acceptable de simplement renvoyer un simple Map. Les gens connaîtront les codes internes du code qu'ils appellent ou au moins y auront facilement accès.

Ma conclusion serait qu'explicite est bonne, ne laissez pas les choses au hasard.

68
Dici

Vous devriez avoir ImmutableMap comme type de retour. Map contient des méthodes qui ne sont pas supportées par l'implémentation de ImmutableMap (par exemple, put) et sont marquées @deprecated dans ImmutableMap.

L'utilisation de méthodes dépréciées génèrera un avertissement pour le compilateur et la plupart des IDE avertiront lorsque des personnes tenteront d'utiliser les méthodes dépréciées.

Cet avertissement avancé est préférable aux exceptions d’exécution, ce qui suggère que quelque chose ne va pas.

36
Synesso

D'un autre côté, si je le laisse comme ça, d'autres développeurs risquent de passer à côté du fait que cet objet est immuable.

Vous devriez le mentionner dans les javadocs. Les développeurs les lisent, vous savez.

Ainsi, je n'atteindrai pas un objectif majeur d'objet immuable; pour rendre le code plus clair en minimisant le nombre d'objets pouvant changer. Pire encore, après un certain temps, quelqu'un pourrait essayer de changer cet objet, ce qui provoquerait une erreur d'exécution (le compilateur n'en avertira pas).

Aucun développeur ne publie son code non testé. Et quand il le teste, il reçoit une Exception où il ne voit pas seulement la raison, mais également le fichier et la ligne où il a essayé d'écrire sur une carte immuable.

Notez cependant que seul le Map lui-même sera immuable, pas les objets qu’il contient.

16
tkausl

si je le laisse comme ça, d'autres développeurs pourraient rater le fait que cet objet est immuable

C'est vrai, mais les autres développeurs devraient tester leur code et s'assurer qu'il est couvert.

Néanmoins, vous avez 2 autres options pour résoudre ceci:

  • tilisez Javadoc

    @return a immutable map
    
  • Choisissez un nom de méthode descriptif

    public Map<String, Integer> getImmutableMap()
    public Map<String, Integer> getUnmodifiableEntries()
    

    Pour un cas d'utilisation concret, vous pouvez même nommer les méthodes mieux. Par exemple.

    public Map<String, Integer> getUnmodifiableCountByWords()
    

Que pouvez vous faire d'autre?!

Vous pouvez retourner un

  • copie

    private Map<String, Integer> myMap;
    
    public Map<String, Integer> foo() {
      return new HashMap<String, Integer>(myMap);
    }
    

    Cette approche doit être utilisée si vous vous attendez à ce qu'un grand nombre de clients modifie la carte et à condition que la carte ne contienne que quelques entrées.

  • CopyOnWriteMap

    les copies sur les collections en écriture sont généralement utilisées lorsque vous devez traiter
    accès simultané. Mais le concept vous aidera également dans votre situation, puisqu'un CopyOnWriteMap crée une copie de la structure de données interne sur une opération mutative (par exemple, ajouter, supprimer).

    Dans ce cas, vous avez besoin d'un wrapper fin autour de votre carte qui délègue tous les appels de méthode à la carte sous-jacente, à l'exception des opérations de mutation. Si une opération de mutation est invoquée, une copie de la carte sous-jacente est créée et toutes les invocations ultérieures sont déléguées à cette copie.

    Cette approche doit être utilisée si vous vous attendez à ce que certains clients modifient la carte.

    Malheureusement Java ne possède pas un tel CopyOnWriteMap. Mais vous pouvez trouver un tiers ou l’implémenter vous-même.

Enfin, vous devez garder à l’esprit que les éléments de votre carte peuvent encore être mutables.

10
René Link

Renvoie définitivement une carte Immutable, la justification étant:

  • La signature de la méthode (y compris le type de retour) doit être auto-documentée. Les commentaires ressemblent au service clientèle: si vos clients ont besoin de compter sur eux, votre produit principal est défectueux.
  • Que quelque chose soit une interface ou une classe n'est pertinent que pour l'extension ou la mise en œuvre. Dans une instance (objet), 99% du temps, le code client ne saura pas ou ne dira pas que quelque chose soit une interface ou une classe. J'ai d'abord supposé que ImmutableMap était une interface. Ce n’est qu’après avoir cliqué sur le lien que j’ai réalisé que c’était une classe.
8
Benito Ciaro

Cela dépend de la classe elle-même. Le ImmutableMap de Guava n'est pas destiné à être une vue immuable dans une classe mutable. Si votre classe est immuable et que sa structure est fondamentalement un ImmutableMap, définissez le type de résultat ImmutableMap. Cependant, si votre classe est mutable, ne le faites pas. Si vous avez ceci:

public ImmutableMap<String, Integer> foo() {
    return ImmutableMap.copyOf(internalMap);
}

Guava va copier la carte à chaque fois. C'est lent. Mais si internalMap était déjà un ImmutableMap, alors tout va bien.

Si vous ne limitez pas le retour de votre classe à ImmutableMap, vous pouvez alors renvoyer Collections.unmodifiableMap Comme suit:

public Map<String, Integer> foo() {
    return Collections.unmodifiableMap(internalMap);
}

Notez que ceci est une image immuable vue dans la carte. Si internalMap change, la copie de Collections.unmodifiableMap(internalMap) mise en cache l'est aussi. Je le préfère toujours pour les getters, cependant.

5
Justin

Cela ne répond pas à la question exacte, mais il est toujours utile de se demander si une carte devrait être renvoyée. Si la carte est immuable, la méthode principale à fournir est basée sur la commande get (clé):

public Integer fooOf(String key) {
    return map.get(key);
}

Cela rend l’API beaucoup plus étroite. Si une carte est réellement requise, elle pourrait être laissée au client de l'API en fournissant un flux d'entrées:

public Stream<Map.Entry<String, Integer>> foos() {
    map.entrySet().stream()
}

Ensuite, le client peut créer sa propre carte immuable ou modifiable selon ses besoins, ou ajouter les entrées à sa propre carte. Si le client a besoin de savoir si la valeur existe, facultatif peut être renvoyé à la place:

public Optional<Integer> fooOf(String key) {
    return Optional.ofNullable(map.get(key));
}
4
Solubris