简体   繁体   中英

How to iterate over a mixed list using foreach?

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> <? 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> <? 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> 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?

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> 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> 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();
}

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>() 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> <? 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.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM