views:

178

answers:

4

I was looking for a decent implementation of a generic lazy non-modifiable list implementation to wrap my search result entries. The unmodifiable part of the task is easy as it can be achieved by Collections.unmodifiableList() so I only need to sort out the the lazy part.

Surprisingly, google-collections doesn't have anything to offer; while LazyList from Apache Commons Collections does not support generics.

I have found an attempt to build something on top of google-collections but it seems to be incomplete (e.g. does not support size()), outdated (does not compile with 1.0 final) and requiring some external classes, but could be used as a good starting point to build my own class.

Is anybody aware of any good implementation of a LazyList? If not, which option do you think is better:

  • write my own implementation, based on google-collections ForwardingList, similar to what Peter Maas did;
  • write my own wrapper around Commons Collections LazyList (the wrapper would only add generics so I don't have to cast everywhere but only in the wrapper itself);
  • just write something on top of java.util.AbstractList;

Any other suggestions are welcome.

EDIT: explanation why I need a lazy list.

I have got a Lucene search result (TopDocs) which is basically a bunch of pointers to Lucene documents. My search result class would take these pointers as an input and return a list of objects which are made of extracted and otherwise processed Lucene documents. By wrapping everything into a lazy list I want to ensure I am not doing expensive processing when unnecessary.

+2  A: 

The solution of Peter Maas you link looks good to me - I strongly suggest you work with that instead spending time reinventing this. Just replace Factory<T> with Supplier<T> (included in google collections). His implementation of subList is quite smart too, though it has some peculiar implications: if you get a subList(), and try to add an element out of the subList's bounds, you won't get an IndexOutOfBoundsException (as a correct subList ought to do), but you'll insert extra elements in the list. The odds are you wouldn't need sublists, so the safest would be to implement that method by throwing UnsupportedOperationException (or construct a LazyList that has an extra flag of whether it is allowed to grow by get() invocations beyond its size: if it created by subList, then it isn't).

size() is supported (automatically, by ForwardingList itself).

Update: Note that, as Kevin said, you did not explained why something like this would really be what you need. Also, perhaps you might want to consider whether something like this could be applicable:

final Supplier<T> supplier = ...;
Map<Integer, T> graphs = new MapMaker()
   .makeComputingMap(
       new Function<Integer, T>() {
         public T apply(Integer index) {
           return supplier.get();
         }
       });

Since List<T> and Map<Integer, T> more or less represent the same abstract data type, and since it appears from your comment that (1) you don't like nulls to be considered as elements (good!), and (2) that your structure might be sparse, where an actual ArrayList would be wasteful.

Dimitris Andreou
What I don't like about Peter Maas's LazyList size() is that it returns the number of already initialized items and not the number of possibly available items. Quoting its Javadoc: "When the {@link #get(int)} method is called with an index greater than thesize of the list, the list will automatically grow in size and return a new object from the specified factory".I would rather like to have this abstract so that subclasses would be forced to provide the list size as it might be impossible in a generic implementation to know it. And yes, I do need sublist support, too.
mindas
@mindas, if this is the feature which stops you from using the implementation, I suggest you roll your own by taking the best one with a compatible license and changing size to your needs.
Yishai
+3  A: 

There's a project which added Generics features to Apache commons-collections:

http://sourceforge.net/projects/collections/

(Commons-collections with Generics)

Grzegorz Oledzki
Thanks for this! I have however realized that commons-collections suffer from the same size() problem (see my comment for Dimitris). This may, however, be useful for other people.
mindas
+2  A: 

I have actually solved this in a different way. Instead of going through lazy and unmodifiable, I simply implemented java.lang.Iterable<T>. The implementation throws UnsupportedOperationException on remove().

I had to slightly modify some other code parts, give up something, but I believe this was the best choice. Iterable allows it to be put on foreach loop.

Sorry to disappoint if this won't be a viable choice for somebody in a similar situation and thanks very much for the ideas.

mindas
+1  A: 

Google-collections and Guava's Lists.transform method gives you the laziness you seek. Sticking with Iterables.transform should be just as good.

But, if you're also concerned that the results should be cached once first created, well... right now, this is the best I'm coming up with, and it won't be very comforting:

List<Supplier<ExpensiveResult>> suppliers =
    ImmutableList.copyOf(Lists.transform(keys,
        new Function<Key, Supplier<ExpensiveResult>>() {
          public Supplier<ExpensiveResult> apply(Key key) {
            return Suppliers.memoize(Suppliers.compose(
                myExpensiveFunction(),
                Suppliers.ofInstance(key)));
          }
        }));

return Lists.transform(suppliers, ThisClass.<ExpensiveResult>supplyFunction());

 . . . 

private static <T> Function<Supplier<T>, T> supplyFunction() {
  return new Function<Supplier<T>, T>() {
    public T apply(Supplier<T> supplier) {
      return supplier.get();
    }
  };
}

Yes, you can laugh. And you probably should. I... don't really recommend this. Still might be less code than what you're currently doing. And I just tested it.. it works.

Kevin Bourrillion
Thanks Kevin! I am still holding myself against using Guava mainly because of it not being finalized (basically the same reasons why you don't have a public Maven repo). As for your solution... I guess it wouldn't even raise an eyebrow of a functional aficionado, but surely I'd have lots to talk about at my next confession once I'd put this in!
mindas
There has been a binary release for a few weeks, and none of the APIs mentioned above are even marked Beta (I think).
Kevin Bourrillion