views:

397

answers:

8

What's the best way to lazily initialize a collection, I'm specifically looking at Java. I've seen some people decide to do this in modification methods (which seems a bit yucky), as follows:

public void addApple(final Apple apple) {       

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

You could refactor the initialization into a method and call it from add/delete/update etc... but it seems a bit yuck. It's often compounded by the fact that people also expose the collection itself via:

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

which breaks encapsulation and leads to people accessing the Collection directly.

The purpose for lazy initialization is purely performance related.

I'm curious to see what other peoples proposed approaches are for this. Any ideas?

+1  A: 

You could refactor the initialization into a method and call it from add/delete/update

That is what I would do, but I would make the method private/protected

protected Collection<Apple> getApples() {
  if (apples == null) {
    apples = new LinkedList<Apple>();
  }
  return apples;
}
James Van Boxtel
Making this protected might not be an option in some instances (such as when the collection is needed for the front-end in a JSF application).
Zack
I'd make that private (protected is evil).
Tom Hawtin - tackline
Of course. It depends on the situation.
James Van Boxtel
+3  A: 

I put the lazy instantiation into the getter for a given function. Usually I instantiate a list lazily to avoid the DB hit if possible. Example:

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);
}

This approach means that other functions (say, removeApples()) won't need to worry about instantiation either. They, too, would just call getApples().

Zack
getApples() should probably be `final` here. At the very least, the non-standard "self-use" should of getApples() should be clearly documented to give sub-classes a chance to get it right.
erickson
@erickson if getApples() were final, that would mean you couldn't call getApples().add(apple), right? Or am I wrong on the use of final?
Zack
No, it would mean a subclass could not override the method. (Use Collections.unmodifiableList for an unmodifiable List.)
Tom Hawtin - tackline
Ahh...then yes, that would make sense. Updating my example.
Zack
Right, if a subclass overrides getApples(), it must make sure to call super.getApples(), or the private member variable "apples" can never be initialized.
erickson
A: 

First you have to decide if the overhead of a function call is worth it. My guess is that you are worried about memory footprint rather than raw speed.

First I would add this. Note it is private to the class

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

Now your addApples looks like this.

public void addApple(final Apple apple) {       

    myApples.add(apple);
}

And anything else that uses the collection internally to your class.

RS Conley
myApples() should be called getApples()
Steve Kuo
I like to distinguish between private properties and public properties. So five years later I can just read that section and know it accessing a private property. Convention is just that convention. It not to be blindly followed.
RS Conley
+1  A: 

For the add case, I'd say initialize it in your constuctor, and as for unwittingly exposing the collection directly, you might consider:

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

to keep people from using the getter to do more than get.

pjz
+3  A: 

To safely lazily initialize a member in a multi-threaded environment, you need some concurrency mechanism to make the initialization atomic and visible to other threads. This cost is paid both during initialization and each time the lazily initialized member is accessed. This ongoing expense can significantly undermine performance. It is very important to profile the effect of lazy initialization. The right choice is going to vary widely depending on the application.

erickson
Wish I could give you more than one upvote.
Joe W.
A: 

I'd keep the lazy initialization in the setter. The getter can simply return an empty collection if the backing collection is null.

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

In this way the backing collection is never instantiated if the getter is called without the setter ever being called.

Steve Kuo
A: 

Put this in a synchronized block, or make it thread-safe through some other mechanism (e.g. compareAndSet, a concurrent container, etc.):

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

because as it stands, you'll clobber apples if multiple threads get inside that if block at the same time.

Joe W.
+1  A: 

The second option is ok:

Initialization into a method and call it from add/delete/update

 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;
  }

...by the fact that people also expose the collection itself via...

No lazy initialization strategy can prevent this. If you don't want the underlaying collection to be exposed ( which is a very good idea ) you have to make it private, make it final ( as in the sample above ) and provide an iterator to access the elements of the collection, not to pass the collection it self.

I think you have it already.

OscarRyz
What's with the dv? :(
OscarRyz