views:

78

answers:

3

Just came across a place where I'd like to use generics and I'm not sure how to make it work the way I want.

I have a method in my data layer that does a query and returns a list of objects. Here's the signature.

public List getList(Class cls, Map query)

This is what I'd like the calling code to look like.

List<Whatever> list = getList(WhateverImpl.class, query);

I'd like to make it so that I don't have to cast this to a List<Whatever> coming out, which leads me to this.

public <T> List<T> getList(Class<T> cls, Map query)

But now I have the problem that what I get out is always the concrete List<WhateverImpl> passed in whereas I'd like it to be the Whatever interface. I tried to use the super keyword but couldn't figure it out. Any generics gurus out there know how this can be done?

+8  A: 

You need to define the method like this:

public <B, C extends B> List<B> getList(final Class<C> cls, final Map<?, ?> query)
Joachim Sauer
what does the return type `<B, C extends B> List<B>` mean?
daedlus
@Deadlus: The return type is just `List<B>`. `<B, C extends B>` are type parameters and they say "this method takes two type parameters, `B` and `C` where `C` must be some sub-type of `B`). Those type-parameters need not be explicitly specified when the method is called usually, since they can be inferred from the context (sometimes it's necessary to specify them, however).
Joachim Sauer
Interesting. I hadn't thought of doing it this way, with two type parameters. I'm still getting a compile error though with:List<Whatever> list = getList(WhateverImpl.class, queryMap);Java complains:found : java.util.List<com.attask.model.ItemImpl>required: java.util.List<com.attask.biz.Item> List<Item> workItems = ListQuery.getList(ctxt, ItemImpl.class, filters);
Sean
@Sean: Eclipse compiles it just fine, but `javac` from the OpenJDK complains :-/ In the past when I hit problems like that it was usually the Eclipse compiler that was more correct, but what good does that do...
Joachim Sauer
@Sean You could force it with: `List<Whatever> list = this.<Whatever, WhateverImpl>getList(WhateverImpl.class, queryMap)` but of course that defeats how you're trying to call it.
Mark Peters
@MarkPeters: once that's necessary, you could get rid of the second type argument altogether and just use `<C> List<C> getList(Class<? extends C> cls)` and call it as `this.getList<Whatever>(WhateverImpl.class)`.
Joachim Sauer
And how exactly do you cast the `List<C>` inside the method to `List<B>` to return it, since they're incompatible types?
Andrei Fierbinteanu
@Joachim: I agree, was just about to say that.
Mark Peters
@Andrei: I think you're mixed up. You can create an `new ArrayList<C>` no problem without casting, and I'm not sure why you think you'd have a `List<C>` that you need to cast to a `List<B>`.
Mark Peters
well, he wants to create a List of the type given by the Class, and then return it as a List of the interface (in this case get a `List<C>` and return it as a `List<B>` or more exactly C is WhateverImpl, and B is Whatever). You have to cast the list containing the actual implementation of the class to the return type.
Andrei Fierbinteanu
@Andrei Fierbinteanu: No, he doesn't want to create a List<WhateverImpl>. I don't know where you're getting that from. It might so happen that all elements of his List<Whatever> are WhateverImpls, but that doesn't imply he ever would have to create a List<WhateverImpl>.
Mark Peters
@Mark, he says he calls it like this: `getList(WhateverImpl.class, query);` and that he ends up with a `List<WhateverImpl>`, so I think that inside `getList` that's what he's creating a `List<WhateverImpl>`. He then wants to use it as a `List<Whatever>` not caring what implementation was used. This would imply that you'd have to cast one to the other in order to return directly `List<Whatever>`. Although it occurred to me now, you can declare `List<Whatever> result` inside the method and then just do `result.add(WhateverImpl)` for every element you want in the list.
Andrei Fierbinteanu
@Andrei Fierbinteanu: He's creating the List generically; as in `new ArrayList<T>()`. The only reason he was getting back a `List<WhateverImpl>` before was because T was being assigned WhateverImpl. By changing the type parameter to `Whatever`, you would never be creating the `List<WhateverImpl>`, since "T" would point to the type you wanted for your List.
Mark Peters
Yeah, I admit it's a weird case and after thinking about it a bit more I'm not sure if what I'm trying to do is even possible. Accepting this answer, though, because it is an interesting approach and probably as close as I'll get. Thanks.
Sean
A: 

Is there any reason that you can't just do this?

public List<T> getList<T>(Map query)
JSBangs
It's not type-safe in any way and unless you're doing some dark, dark magic you usually need some type information at runtime for this kind of operation.
Joachim Sauer
Nor does it compile. Though I don't agree that it takes dark, dark magic for a signature like this to have valid use. For example, change "getList" to "createList" and "query" to "properties" and you might have a smart factory method that creates a List given the properties you have in the map `(map.put(MapProperties.INSERTION_COMPLEXITY, new BigO(1)))` anyone?
Mark Peters
@MarkPeters: let me clarify: it takes dark, dark magic to implement this in a type-safe way ;-)
Joachim Sauer
You're saying this isn't type-safe: `public List<T> getList(Map query) { return new ArrayList<T>(); }`? What about `Collections.emptyList()`, that's not type-safe either? (Ahh, I take it back for emptyList since it passes back a singleton, resulting in unchecked casts... Which probably is the dark magic you're talking about.)
Mark Peters
+2  A: 

The only way I can think this would work is like this:

public <T extends Whatever> List<? extends Whatever> get(Class<T> clazz, Map query) {
 return new ArrayList<T>(); // do whatever it is you do here to return a List<T>
}

and then call it like:

List<? extends Whatever> l = get(WhateverImpl.class, query);
for (Whatever w : l) {
    // do something
}

You can't return List<Whatever> directly since you won't be able to cast List<WhateverImpl> to it inside the get method (they're not compatible).

Andrei Fierbinteanu
`List<? extends Whatever>` is not the same as `List< Whatever >`. For example, you cannot add new elements to wildcarded list without unchecked warnings.
Alexander Pogrebnyak
Yes I am aware. But it would allow you to get the elements as `Whatever` which is what I thought was needed. Anyway I realized I didn't quite understand what the asker wanted.
Andrei Fierbinteanu