Monday, 6 November 2006

Generics, Iterables and Arrays - more banging of the head

Yes, I'm back again, with another Java 5 example that caused me to bang my head against the wall (see yesterday).

Lets recap the scenario - a CompositeCollection wraps a list of collections and makes them look like a single collection. Here's the class outline again: (I've removed the simple one and two argument methods from yesterdays example as they're not relevant to today):

public class CompositeCollection<E> implements Collection<E> {

  private Collection<Collection<E>> all;

  public void addComposited(Collection<E>[] arr) {
    all.addAll(Arrays.asList(arr));
  }
}

Well we know varargs doesn't work here from yesterday. What if we try to improve the functionality in a different way. What if we make it so that you can pass in any Iterable, wouldn't that make it simpler? After all, Iterable is the new super-interface to all things in the JDK that can be iterated around:

public class CompositeCollection<E> implements Collection<E> {

  private Collection<Collection<E>> all;

  public void addComposited(Iterable<Collection<E>> it);
    for (Collection<E> c : it) {
      all.add(c);
    }
  }
}

This is all fine right? I can pass in a List, a Collection or an array right? Wrong.

Why? Well arrays don't implement Iterable. Did you know that? Well, if you did then three gold stars to you! But it was against my expectations.

The reason I expected an array to implement Iterable is because the new foreach loop works on Iterable objects. Thus I naturally expected all arrays to implement Iterable. But it isn't so. And that made me bang my head against the wall. Again.

Now one reason for this may be that an Iterator defines the remove method, but that could easily have thrown UnsupportedOperationException as it is defined to do.

The second possible reason is that it clashes with generics. Consider the case where arrays implement Iterable. Of course an Integer[] would have to implement Iterable<Integer>. But that generification causes problems:

Integer[] arrayInt = new Integer[10];
Iterable<Integer> iterInt = arrayInt;  // assume array implements Iterable
Iterable<Number> iterNum1 = iterInt;   // illegal due to generics

Number[] arrayNum = arrayInt;          // always been legal in Java
Iterable<Number> iterNum2 = arrayNum;  // legal or illegal???????????

Thus, an Iterable<Number> can be obtained via one sequence of code (array assign) but not another (generic assign). So, the Java5 expert groups chose to not make an array implement Iterable - it would have clashed with generics.

And yet, the irony of all this is that there is in fact no risk here, and the final line could have been legal. Iterable is a read-only interface (wrt generics), so there wasn't actually anything that could go wrong!!!

To be fair, I'm only speculating on what the thinking was. All we know is that arrays don't implement Iterable, and the foreach loop is coded differently for arrays as opposed to Iterable instances.

And hence API designers will have to choose to make their API only accept Collection, and force users to use Arrays.asList. Or alternatively have two versions of the API method, one for collections and one for arrays.

3 comments:

  1. It would be nice if arrays implemented a lot of stuff, like... java.util.List. Notice how the JDK duplicates a ton of algorithms, implementing them once for Lists and once for every type of array? (primitive arrays + Object[]) In fact there's no good reason for arrays to exist at all; collections should have been there from the start, with an efficient implementation of ArrayList etc. But hey, its Enterprise, nothing works.

    ReplyDelete
  2. You'd be better off defining CompositeCollection with wildcards:

    private final Collection> all;

    public void addComposited(Iterable> iter) {

    Or just use the return this idiom:

    public CompositeCollection addComposite(Collection collection) {

    ReplyDelete
  3. One of my colleagues had some similar woes with arrays and generics; my advice: "Stop using arrays". He prefers arrays syntactically, so he chose not to use generics, instead.

    It's a shame that the array[index] syntax wasn't extended to Lists..

    The RFE for operator overloading has a ton of comments, http://bugs.sun.com/bugdatabase/view_bug.do;:YfiG?bug_id=4905919 - maybe it's time to start blogging about it so that it happens. ;)

    ReplyDelete