1

I have a class which maintains a list of features of the class. These features change infrequently compared to the reads. The reads are almost always iterations through the feature list. Because of this, I'm using a CopyOnWriteArrayList.

I want to have a function like this:

function Feature[] getFeatures() {
  .. implementation goes here ..
}

I admit, the reason may be a bit of laziness. I'd like to write code like this:

for (Feature f: object.getFeatures()) {
  .. do something interesting ..
}

rather than this:

Iterator<Feature> iter = object.getFeatureIterator();
while (iter.hasNext()) {
  Feature f = iter.next();
  .. do something interesting ..
}

The main question is - am I being lazy here? I'm going to follow this pattern a lot, and I think the first chunk of code is far easier to maintain. Obviously, I would never change the underlying array, and I would put this in the documentation.

What is the proper way to handle this situation?

Erick Robertson
  • 32,125
  • 13
  • 69
  • 98

5 Answers5

1

I don't understand your reason: return a List<Feature>, use your CopyOnWriteArrayList or an unmodifiable copy, and use the foreach. Why do you specifically want an array?

Dave Newton
  • 158,873
  • 26
  • 254
  • 302
  • I don't know. Maybe this is just some brain fart where I'm missing the totally obvious thing. Actually, that's exactly what it seems from your answer here. – Erick Robertson Nov 18 '11 at 23:57
1

Just call the toArray method on the list:

public Feature[] getFeatures() {
    return this.featureList.toArray(new Feature[this.featureList.size()]);
}

Note that the foreach syntax can be used with all the Iterable objects, and List is Iterable, so you could just have

public List<Feature> getFeatures() {
    return this.features;
}

and use the same foreach loop. If you don't want the callers to modify the internal list, return an unmodifiable view of the list:

public List<Feature> getFeatures() {
    return Collections.unmodifiableList(this.features);
}
JB Nizet
  • 678,734
  • 91
  • 1,224
  • 1,255
  • Will `Collections.unmodifiableList` copy over the same underlying array into the `List` it returns? – Erick Robertson Nov 19 '11 at 00:02
  • No. It's just a proxy for the wrapped list, which delegates all read-only operations to the wrapped list (size(), get(), etc.), and throws an exception for all modifying methods (set(), add(), etc.). – JB Nizet Nov 19 '11 at 00:04
1

Class CopyOnWriteArrayList implements Iterable, which is all you need to use the sugared for loop syntax. You don't need to get hold of an Iterator explicitly in the case you describe above.

Did you find that it doesn't compile?

seh
  • 14,999
  • 2
  • 48
  • 58
1

you can clone the List

public List<Feature> getFeatures() {
    return (List<Feature>)this.features.clone();
}

cloning a copyOnWriteArrayList doesn't copy the underlying array

ratchet freak
  • 47,288
  • 5
  • 68
  • 106
0

The copy-on-write (COW) nature of a CopyOnWriteArrayList object means that at every modification of the list you get a new instance of its underlying array, with all entries but the modification copied from the previous instance.

To give you a coherent view while iterating over the list when other threads keep changing its content, a call to the iterator() method ties the iterator to the array, not the list. When a modification changes the list, a new array holds the new content, but the iterator continues to run through the old array that was available when iterator() was called. This means that you walk through a coherent snapshot of the list.

(In contrast, a loop such as for (int i = 0; i < list.size(); ++i) doSomethingWith(list.get(i)); does not protect you from modifications from other threads, and you may easily run off the end of the list if between a call to list.size() and the corresponding list.get(i), some elements have been deleted!)

Since the for each style for-loop uses iterators under the covers, you get this coherence guarantee. You also get it when using the forEach() method. This iterates (using array indexing) within the array available at the time forEach() is invoked.

Finally, the clone() method essentially takes a similar snapshot. If your code is the only place you use the cloned version, or you make it unmodifiable, you'll be consulting the original snapshot. The COW nature of the original shields your copy from changes to the original. If you don't want to rely on clone(), which has issues, you can copy out the list data, but that involves at least copying the array with another allocation.