Java generic method type argument
Asked Answered
U

4

8

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?

Undertint answered 1/7, 2014 at 9:49 Comment(6)
Which line do you get the error on?Taligrade
See #24505814Mycorrhiza
It points to the first line of the class, so nothing interestingGratuitous
@Mycorrhiza It was helpful, however the library uses the Gson's TypeTokenGratuitous
@CsehTamás: You can wrap a Type produced by Guava's TypeToken into Gson's TypeToken using TypeToken.get() (if you can use Guava on Android, of course).Mycorrhiza
@Mycorrhiza Thanks, maybe I'll try that if there isn't another solution.Gratuitous
U
2

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>>() {...});
Undertint answered 29/9, 2017 at 7:26 Comment(1)
Three years later, that's some dedication!Dovekie
L
1

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.

Laurent answered 6/10, 2016 at 17:50 Comment(0)
Q
0

To deserialize to varying classes that implement ContentItem, I think you will need to use a custom GSON instance that has a TypeAdapter.

http://www.javacreed.com/gson-typeadapter-example/

Quarter answered 14/7, 2014 at 8:6 Comment(0)
W
-1

What about using

private <T implements ContentItem> void download(String url) {
....

Since the classes implement the ContentItem interface, they do not extend the interface

Warnock answered 7/7, 2014 at 13:50 Comment(1)
It's not valid. You have to write extends even if it's an interface.Gratuitous

© 2022 - 2024 — McMap. All rights reserved.