views:

186

answers:

3

Hi,

I'm wondering how to iterate over a List with mixed contents using foreach. See the example code below.

public class GenericsForeach {

    class A {
        void methodA() {
            System.out.println(getClass().getSimpleName() + ": A");
        }
    }

    class B extends A {
        void methodB() {
            System.out.println(getClass().getSimpleName() + ": B");
        }
    }

    void test() {

        List<A> listOfA = new ArrayList<A>();
        listOfA.add(new A());

        List<B> listOfB = new ArrayList<B>();
        listOfB.add(new B());

        List<? super A> mixed = new ArrayList<A>();
        mixed.addAll(listOfA);
        mixed.addAll(listOfB);

        Iterator<? super A> it = mixed.iterator();
        while (it.hasNext()) {
            A item = (A) it.next();
            item.methodA();
        }

        // XXX: this does not work
        // for (A item : mixed) {
        // item.methodA();
        // }
    }

    public static void main(String[] args) {
        new GenericsForeach().test();
    }
}

I construct two lists with different, but related, content types A and B (B extends A). I add the two lists to a 'mixed' list, which I declare to contain <? super A> types. Since this mixed list is 'consuming' items of type A (or B) I applied Bloch's PECS rule (Producer Extends, Consumer Super) to determine that I need <? super A> here.

So far, so good. But now when I want to iterate over this mixed list, I can only seem to do it with an Iterator<? super A>, and a cast A item = (A) it.next(). When I try to use a foreach loop (see commented-out code), no joy:

Type mismatch: cannot convert from element type capture#8-of ? super GenericsForeach.A to GenericsForeach.A

Eclipse even helpfully offers to

Change type of 'item' to '? super A'

but this results in disaster:

for (? super A item : mixed) {
    item.methodA();
}

So I don't know. Eclipse doesn't seem to know. Does anybody else here know if this is possible, and if it's not, why not?

+8  A: 

You want just List<A> for mixed. My reasoning:

  • you want to be able to add items which are of type A, so it can't be List<? extends A> - that would include List<B>, which you can't add an A to.
  • you want to be able to guarantee that items which you fetch are of type A, so it can't be List<? super A> as that could be a List<Object> containing non-A elements.

So you end up with:

List<A> mixed = new ArrayList<A>();
mixed.addAll(listOfA);
mixed.addAll(listOfB);

for (A item : mixed) {
  item.methodA();
}
Jon Skeet
I agree.. Wanted to say the same, but you are way too quick for me :)
Fortega
I'd hoped it would be more complicated than that... (-; Thanks!
Adriaan Koster
A: 

since all of the are objects,why not the following:

for(Object O : listOfA)

   {
      //code goes here

   }
Phobia
That means he probably has to cast each of the objects to (A), which is not a nice solution...
Fortega
A: 

Everyone here is correct. You want use List<A>.

But generics and assignments can be confusing, so a little more explanation is in order.

First, the problem you may have found is that you can't do this: List<A> = new List<B>(). The compiler won't let you assign a sub-type in to a super-type listing using generics. This is a little confusing, but it prevents problems with type mis-matches. (More detail can be found here: http://java.sun.com/docs/books/tutorial/java/generics/subtyping.html.) The correct terminology for this is List<? extends A> = new List<B>(). This tells the compiler that your assignment is legal.

At the same time, this syntax can confuse you to believing that <? extends A> means that all elements in this variable extend A. This isn't true - the syntax just a way to inform the compiler of legal assignments.

So, you want to use List<A> = new List<A> and then assign the elements to List<A> using addAll(). This is legal because the method addAll checks to make sure each element is valid before pushing it to the collection.

Jonathan B