tags:

views:

91

answers:

2

I have a heterogeneous List that can contain any arbitrary type of object. I have a need to find an element of the List that is of a certain type. Looking through the answers of other generics related questions, I'm not finding exactly what I need.

Here's an example of what I'm trying to accomplish:

List <Object> list = new ArrayList <Object>(); 

...

private someMethod() {
   Customer cust = findInList( Customer.class );
   Invoice inv = findInList( Invoice.class );
}

So, how do I define findInList using generics? I gather that type erasure causes issues here and I don't know as much about that as I probably should, but I'd rather not define multiple "find" methods since there could be dozens of different types of objects living in the List.

+5  A: 

You can define the method using Class.isInstance() and Class.cast() method. Here is a sample implementation (with extra argument for the list):

static <T> T findInList(List<?> list, Class<T> clazz) {
  for (Object o : list) {
    if (clazz.isInstance(o)) {
      return clazz.cast(o);
    }
  }
  return null;
}

Update: I would recommend against putting multiple types in the Collection. It's usually a sign that either need a custom datatype (e.g. Transaction) or a Tuple value.

notnoop
You probably want to use isAssignableFrom here, as getting the exact instance won't matter, you care more about if it extends or implements the relevant type.
Yishai
isInstanceOf( should be isInstance(
Shaun
Perfect. I was forgetting the isInstanceOf method.
SwimsZoots
@Yishai, isInstance does the right thing as it is equivalent to `instanceof`. `isAssignableFrom` is useful if you have a `Class` argument. In other words `Object.class.isInstance("m")` is `true`.
notnoop
@Shaun, thanks! Corrected.
notnoop
This is as close as you can get to correct in Java, but keep in mind that Class.isInstance() (just like instanceof, or any other runtime check) can't "see" generic type parameters due to erasure. That means if you (for example) add a Set<Foo> to the List, all you'l know is that it's a Set. There's no way to find out at runtime what type of Set it is. You'll get an "unchecked" warning if you do this, so at least you'll know when you need to be careful.
Laurence Gonsalves
+5  A: 

You can use Typesafe Heterogeneous Container pattern described by Josh Bloch. Here is an example from Josh's presentation:

class Favorites {
    private Map<Class<?>, Object> favorites =
            new HashMap<Class<?>, Object>();

    public <T> void setFavorite(Class<T> klass, T thing) {
        favorites.put(klass, thing);
    }

    public <T> T getFavorite(Class<T> klass) {
        return klass.cast(favorites.get(klass));
    }

    public static void main(String[] args) {
        Favorites f = new Favorites();
        f.setFavorite(String.class, "Java");
        f.setFavorite(Integer.class, 0xcafebabe);
        String s = f.getFavorite(String.class);
        int i = f.getFavorite(Integer.class);
    }
}

You can easily extend this to support List of favorites per type instead of a single value.

Chandra Patni
+1 I would recommend this approach a bit more.
notnoop
+1 I was going to recommend the same.
BalusC
Unless you need support for `null` values, `setFavorite(T thing)` could use `thing.getClass()` as key.
rsp
rsp, The value type may not be the subtype of what you want to save. For instance, `f.setFavorite(Appendable.class, new StringBuilder("Lorem ipsum dolor sit amet"));`
Chandra Patni
`public <T> void setFavorite(T thing)` is a useful helper. The canonical signature is `public <T> void setFavorite(Class<T> klass, T thing)`
Chandra Patni
But a `Map<Class<?>, List<?>>` is not equivalent to a `List<?>` in terms of what it can represent. I don't think this meets the OP's stated requirements.
Stephen C