2009-04-20 10 views
2

Quelle est la meilleure façon d'initialiser paresseusement une collection, je regarde spécifiquement Java. Je l'ai vu certaines personnes décident de le faire dans les méthodes de modification (qui semble un peu dégueu), comme suit:Initialisation par défaut d'une collection

public void addApple(final Apple apple) {  

    if (this.apples == null) { 
     apples = new LinkedList<Apple>(); 
    } 
    this.apples.add(apple); 
} 

Vous pouvez factoriser l'initialisation en une méthode et l'appeler à partir d'ajout/suppression/mise à jour, etc. .. mais il semble un peu beurk. Il est souvent aggravé par le fait que les gens exposent également la collection elle-même via:

public Collection<Apple> getApples() { 
    return apples; 
} 

qui rompt l'encapsulation et conduit à des personnes accédant à la collection directement.

Le but de l'initialisation différée est purement lié aux performances.

Je suis curieux de voir ce que d'autres personnes proposent des approches pour cela. Des idées?

Répondre

3

J'ai mis l'instanciation paresseuse dans le getter pour une fonction donnée. Habituellement, j'instancie paresseusement une liste pour éviter le hit DB si possible. Exemple:

public final Collection<Apple> getApples() { 
    if (apples == null) { 
     // findApples would call the DB, or whatever it needs to do 
     apples = findApples(); 
    return apples; 
} 

public void addApple(final Apple apple) {  
    //we are assured that getApples() won't return 
    //null since it's lazily instantiated in the getter 
    getApples().add(apple); 
} 

Cette approche signifie que d'autres fonctions (par exemple, removeApples()) ne seront pas à se soucier de l'instanciation soit. Eux aussi appellent simplement getApples().

+1

getApples() devrait probablement être 'final' ici. À tout le moins, le «self-use» non standard de getApples() devrait être clairement documenté pour donner aux sous-classes une chance de bien faire les choses. – erickson

+0

@erickson si getApples() était définitif, cela signifierait que vous ne pouvez pas appeler getApples(). Add (apple), n'est-ce pas? Ou ai-je tort sur l'utilisation de la finale? –

+1

Non, cela signifierait qu'une sous-classe ne pourrait pas remplacer la méthode. (Utilisez Collections.unmodifiableList pour une liste non modifiable.) –

1

Vous pouvez factoriser l'initialisation en une méthode et l'appeler d'ajouter/supprimer/mise à jour

Voilà ce que je ferais, mais je ferais privé/protégé la méthode

protected Collection<Apple> getApples() { 
    if (apples == null) { 
    apples = new LinkedList<Apple>(); 
    } 
    return apples; 
} 
+0

Cette protection peut ne pas être une option dans certains cas (par exemple lorsque la collecte est requise pour le frontal dans une application JSF). –

+0

Je rends cela privé (protégé est le mal). –

+0

Bien sûr. Ça dépend de la situation. –

0

Vous devez d'abord décider si le coût d'un appel de fonction en vaut la peine. Je suppose que vous êtes préoccupé par l'empreinte mémoire plutôt que par la vitesse brute.

D'abord, je voudrais ajouter ceci. Notez qu'il est privé à la classe

private Collection<Apple> myApples() { 
    if (this.apples == null) { 
     this.apples = new LinkedList<Apple>(); 
    } 
    return this.apples; 
} 

Maintenant votre addApples ressemble à ceci.

public void addApple(final Apple apple) {  

    myApples.add(apple); 
} 

Et toute autre chose qui utilise la collection en interne à votre classe.

+0

myApples() doit s'appeler getApples() –

+0

J'aime faire la distinction entre les propriétés privées et les propriétés publiques. Donc, cinq ans plus tard, je peux simplement lire cette section et la connaître en accédant à une propriété privée. La convention est juste cette convention. Cela ne doit pas être suivi aveuglément. –

1

Pour le cas d'ajout, je dirais que initialize dans votre constuctor, et comme pour exposer involontairement la collection directement, vous pourriez envisager:

public Collection<Apple> getApples() { 
    return Collections.unmodifiableCollection(apples); 
} 

pour empêcher les gens d'utiliser le getter à faire plus obtenir.

3

Pour initialiser paresseusement en toute sécurité un membre dans un environnement multithread, vous avez besoin d'un mécanisme de concurrence pour rendre l'initialisation atomique et visible aux autres threads. Ce coût est payé à la fois lors de l'initialisation et chaque fois que le membre paresseusement initialisé est accédé. Cette dépense continue peut considérablement nuire à la performance. Il est très important de profiler l'effet de l'initialisation paresseuse. Le bon choix va varier considérablement selon l'application.

+0

Je voudrais pouvoir vous donner plus d'un upvote. –

0

Je garderais l'initialisation paresseuse dans le setter.Le getter peut simplement renvoyer une collection vide si la collection de sauvegarde est nulle.

public Collection<Apple> getApples() { 
    return apples==null ? Collections.emptyList() : apples; 
} 

De cette façon, la collection de support est instancié jamais si le getter est appelé sans le poseur jamais appelé.

0

Mettre cela dans un bloc synchronisé, ou le rendre thread-safe par un autre mécanisme (par exemple compareAndSet, un conteneur simultané, etc.):

if (this.apples == null) { 
    apples = new LinkedList<Apple>(); 
} 

parce que son état actuel, vous écraserait les pommes si plusieurs threads entrent à l'intérieur de ce bloc en même temps.

1

La deuxième option est ok:

Initialisation dans une méthode et appeler à partir d'ajout/suppression/mise à jour

private Collection<Apple> apples; 
.... 
private final Collection<Apple> getApples() { 
    if(apples == null) { 
      apples = new LinkedList<Apple>(); 
    } 
    return apples; 
} 

public final void addApple(Apple a) { 
     getApples().add(a); 
} 
public final void removeApple(Apple a) { 
     getApples().remove(a); 
} 

public Iterator<Apple> iterator() { 
    return getApples().iterator; 
    } 

... par le fait que les gens exposent également la collection elle-même via ...

Aucune stratégie d'initialisation paresseuse ne peut empêcher cela. Si vous ne voulez pas que la collection sous-jacente soit exposée (ce qui est une très bonne idée) vous devez le rendre privé, le rendre final (comme dans l'exemple ci-dessus) et fournir un itérateur pour accéder aux éléments de la collection, non passer la collection elle-même.

Je pense que vous l'avez déjà.

+0

Qu'est-ce qu'il y a avec le dv? :( – OscarRyz