views:

128

answers:

4

Hi!

I have a collection of elements of type B and C, which all extends A. I need to filter the collection to get elements of only B type.

Is there any way to do it except:

for (A a : initCollection) {
  if (a instanceof B) {
    newCollection.add(a)?
  }
}

Thanks

+2  A: 

AFAIK, there is no plain way to do it without adding code to classes A, B, and C. The real question is: Why do you need to filter out elements of a specific type? It goes against the very idea of sub-typing in OOP.

Little Bobby Tables
why it goes so? I've got a collection of super type, which of course can have different subtype instances. Now I want to have exact class. Why not? where is mistake?
glaz666
It's not a mistake. While an OO purist might say that for whatever manipulation you want to do, make every object in the main collection implement a method to do it (with the higher-level objects implementing a noop method) that takes a performance hit often enough that doing what you are asking is quite frequent.
DJClayworth
+1 The desire to do this seems like a code smell to me. There are circumstances where it might be the right approach, but I think in most cases, changing the design would be a better option.
Carl Manaster
@glaz666: If you have to filter the sub-types from the super-type, it insinuates that the super-type is ill-designed. It's not always the case, but in many situations the problems can be solved without using reflection: E.g., using the Visitor pattern.
Little Bobby Tables
+3  A: 

I don't see anything wrong with doing it as per your example. However, if you want to get fancy you could use Google's Guava library, specifically the Collections2 class, to do a functional-style filtering on your collection. You get to provide your own predicate, which can of course do the instanceof thing.

Collection<Object> newCollection = Collections2.filter(initCollection, new Predicate<Object>() { 
    public boolean apply(Object o) {
        return !(o instanceof String);
    } 
});
Carl Smotricz
all right, thanks. The same can be accompllishen by apache's commons-collections library
glaz666
@glaz: it's only not generic. Guava (Google Collections) is. I wouldn't miss parameterized types.
BalusC
I'm a big fan of google collections. But I don't see any advantages which google collections can give in this particular example. There will be one more project-dependency, more lines of code, more complicated code, almost sure that it'll be slower. And no any pros.
Roman
@Roman: I mentioned Guava only as an alternative; it's mostly a matter of taste. But you may be surprised to note that the Guava solution offers *better* performance. The OP's solution creates a new data structure with a subset of the original data; that's a lot of `new`s and work for the garbage collector. `Collections2`, on the other hand, offers a view into the original collection, i.e. no new data structure (apart from a small fixed-cost support data structure, I'd wager). This is a nice example for the greater efficiency of functional programming.
Carl Smotricz
@Carl Smotricz: could you attach a code example (or at least mention methods from Collection2) which do the things you've described, please?
Roman
Sure. From `Collections2.filter()` javadoc: "Returns the elements of `unfiltered` that satisfy a predicate. **The returned collection is a live view of unfiltered**; changes to one affect the other." (emphasis mine).
Carl Smotricz
@Roman: I was so free to edit it in. It has however one disadvantage. The returned collection can't be generified to the desired type.
BalusC
@BalusC: Thanks for the edit! You realized I was too lazy ;) Are you German, by chance? s/I was so free to edit/I took the liberty of editing/
Carl Smotricz
@Carl, no, I'm Holland-born. Only my last name is German (grand-grandfather was German). Thanks for the language lesson though, much appreciated.
BalusC
@BalusC right, the returned collection has the wrong type. see my answer for the solution to that.
Kevin Bourrillion
A: 

If you don't want to create new collection you can remove odd elements from existing one:

Iterator<A> it = initCollection.iterator();
while (it.hasNext()){
   if (it.next() instanceof C) {
      it.remove();
   }
}
Roman
+6  A: 

Guava was alluded to in other answers, but not the specific solution, which is even simpler than people realize:

Iterable<B> onlyBs = Iterables.filter(initCollection, B.class);

It's simple and clean, does the right thing, only creates a single instance and copies nothing, and doesn't cause any warnings.

(The Collections2.filter() method does not have this particular overload, though, so if you really want a Collection, you'll have to provide Predicates.instanceOf(B.class) and the resulting collection will still sadly be of type Collection<A>.)

Kevin Bourrillion
Be aware that with this solution, every time you iterate over overBs, the filtering logic will be executed. This can either be a good thing (if initCollection is updated and you want onlyBs to reflect the changes) or a bad thing (if you are expecting a copy, or pass onlyBs to some code that iterates over it many times). If you want a copy, just create a collection from the output of filter().
NamshubWriter