Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Lazy non-modifiable list in Google Collections

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.

like image 315
mindas Avatar asked Apr 22 '10 21:04

mindas


4 Answers

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.

like image 157
Kevin Bourrillion Avatar answered Oct 18 '22 05:10

Kevin Bourrillion


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

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

(Commons-collections with Generics)

like image 27
Grzegorz Oledzki Avatar answered Oct 18 '22 05:10

Grzegorz Oledzki


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.

like image 24
mindas Avatar answered Oct 18 '22 04:10

mindas


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.

like image 2
Dimitris Andreou Avatar answered Oct 18 '22 06:10

Dimitris Andreou