I have a problem with generic method's explicit type arguments. I know I can do this:
Foo.<Bar>function();
assuming there is a
void <T> function() {...}
function in Foo class. The exact problem is:
I would like to download some content (Android with Ion)
These contents are similar (Article, BlogArticle, ...), all implements a ContentItem interface
At the moment the downloading looks like this:
news for example
private void downloadNews() {
Ion.with(this)
.load(URL_NEWS)
.as(new TypeToken<List<Article>>(){})
.setCallback(new FutureCallback<List<Article>>() {
@Override
public void onCompleted(Exception e, List<Article> result) {
// do something with result
}
});
}
If I want to download blog articles, I have to change url and Article class only (for BlogArticle).
I tried to make a generic function like this:
private <T extends ContentItem> void download(String url) {
Ion.with(this)
.load(url)
.as(new TypeToken<List<T>>(){})
.setCallback(new FutureCallback<List<T>>() {
@Override
public void onCompleted(Exception e, List<T> result) {
// do something with result
}
});
}
and call that function
this.<Article>download(url);
It's ok, compile fine. After running I get
java.lang.ClassCastException: com.google.gson.internal.LinkedTreeMap cannot be cast to com.my.packagename.model.ContentItem
The problem is that it doesn't use the explicit class for mapping Json to pojo.
Can you suggest me a generic solution?
Years later, but I thought it can be useful for someone. I ended up with a simpler solution. It's just a simple, truncated version but you can get the idea:
public static <T> void asList(Context context, String url, Class<T[]> clazz, final FutureCallback<List<T>> callback) {
Ion.with(context)
.load(url)
.as(clazz)
.setCallback(new FutureCallback<T[]>() {
@Override
public void onCompleted(Exception e, T[] result) {
callback.onCompleted(e, Arrays.asList(result));
}
});
}
And use like:
asList(context, url, YourObject[].class, new FutureCallback<List<YourObject>>() {...});
I think there is no way to implement what you want in a generic fashion using the TypeToken approach. In fact, note that for type tokens to work, you need to create an anonymous inner class. By doing this, you effectively create a new classfile, whose supertype is reified as List<Article>
. In other words it's like if you had the following declaration:
class ArticleToken extends TypeToken<List<Article>> { ... }
If you were to write the above declaration yourself, you would observe that the classfile ArticleToken.class keeps track of the generic supertype in the so called Signature
attribute (see JVMS). Hence, this trick allows you to access such generic supertype later on, by calling Class.getGenericSupertype
. In other words, it's an idiom to fake reified generics.
If you turn your code into a generic method and replace Article with the type-variable T, what happens is that the type-token you create is like this:
class GenericToken extends TypeToken<List<T>> { ... }
So, the info about T is stored as is in the classfile, and if reflectiopn asks for the generic supertype of the type token, you simply get TypeToken<List<T>>
back and not TypeToken<List<Article>>
as you'd expect which then causes the problem you are seeing. What you need to make this work is true reified generics, where binding T to Article at the method call site would affect the runtime behavior of new TypeToken<List<T>>
- but sadly that's not the case with Java which uses erased generics.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With