Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Java generic method type argument

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?

like image 408
Tamás Cseh Avatar asked Jul 01 '14 09:07

Tamás Cseh


2 Answers

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>>() {...});
like image 129
Tamás Cseh Avatar answered Sep 25 '22 13:09

Tamás Cseh


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.

like image 36
Maurizio Cimadamore Avatar answered Sep 24 '22 13:09

Maurizio Cimadamore