3

After successfully writing one method which does what it should (much shortened sample below) I want to refactor it to not be limited to return a List of MyEntity but instead a List<SomeType extends MyParentEntity>. So it should be able to accept only those types extending my MyParentEntity but able to specify another type (List<MyOtherEntity>, List<MyAwesomeEntity> etc.).

Shortened example:

 public static List<MyEntity> getFavList(Context context) {

        String prefKey = buildKey( new MyEntity() );
        List<MyEntity> entityList = new ArrayList<MyEntity>();

        SharedPreferences settings = context.getSharedPreferences(prefKey, 0);

        GSON gson = new GSON();
        MyEntity entity = gson.fromJson(settings.getString(0, null), MyEntity.class);

        entityList.add(entity);

        return entityList;
    }

I googled a lot but I didn't find the correct approach to make this work without any compiler errors. I'd appreciate any pointers leading to a solution.

Stefan Hoth
  • 2,675
  • 3
  • 22
  • 33

4 Answers4

2

The information about concrete implementation of MyParentEntity has to be taken from method params (it can't be inferred from the return type - nonsense). So you have to modify the params.

I would try:

public static <T extends MyParentEntity> List<T> getFavList(Context context, Class<T> clazz) {

        String prefKey = buildKey( clazz.newInstance() );
        List<T> entityList = new ArrayList<T>();

        SharedPreferences settings = context.getSharedPreferences(prefKey, 0);

        GSON gson = new GSON();
        MyEntity entity = gson.fromJson(settings.getString(0, null), clazz);

        entityList.add(entity);

        return entityList;
    }
Jiri Kremser
  • 12,471
  • 7
  • 45
  • 72
2

You will need to pass in the Class and the object you are passing to buildKey.

public static <T extend MyParentEntity> List<T> getFavList(
    Context context, Class<T> clazz, T key
) {

    String prefKey = buildKey(key);
    List<T> entityList = new ArrayList<T>();

    SharedPreferences settings = context.getSharedPreferences(prefKey, 0);

    GSON gson = new GSON();
    MyEntity entity = gson.fromJson(settings.getString(0, null), clazz);

    entityList.add(entity);

    return entityList;
}

(I don't know what buildKey is supposed to be.)

Tom Hawtin - tackline
  • 145,806
  • 30
  • 211
  • 305
  • Couldn't `clazz` be inferred from `key.class` instead of receiving it as an argument? – Xavi López Sep 18 '12 at 16:41
  • @XaviLópez There's no reason why it should be the same class, and in general plenty of reasons not to have the restriction. – Tom Hawtin - tackline Sep 18 '12 at 18:12
  • +1'd. Not just because of this. Assuming the default constructor for all keys might be a little bit daring (maybe not `getFavList`'s responsibility)· – Xavi López Sep 18 '12 at 19:10
  • @TomHawtin-tackline I used your solution and adapted it to the full version. Along the way I got rid of the key-argument and changed it to be dependable on T's class. Thank you and everyone else for their help. – Stefan Hoth Sep 18 '12 at 23:57
1

As Java has type erasure (read up about it here http://docs.oracle.com/javase/tutorial/java/generics/erasure.html), you won't be able to easily just get the type parameter given to a List<Something>.

There are two places in Java where type erasure can be tricked though. One is by creating a subclass of something that has been parameterized. Such trick is used by Guava's as well as Gsons TypeToken.

See an example bellow:

List<MyThing> l = gson.fromJson("[{}, {}]", new TypeToken<List<MyThing>>(){}.getType());

Notice that you're creating an anonymous class here, that derrives from a parameterized TypeToken - as I explained before, this allows us to keep the List<MyThing> Type during Runtime.

More can be found in the Gson docs here: https://sites.google.com/site/gson/gson-user-guide or on StackOverflow, here: How to deserialize a list using GSON or another JSON library in Java?

PS: I see that you just want to return a single element in this list (for now?)...? My answer is about actually unmarshalling such list of elements from JSON - which I guess you'll be doing next? ;-) If you want to return just "a single element in a list", use Collections.singletonList() or Guava's ImmutableList.of() as they're immutable (a good thing) and have way lower memory footprint than a "normal list".

Cheers, Konrad

Community
  • 1
  • 1
Konrad 'ktoso' Malawski
  • 13,102
  • 3
  • 47
  • 52
  • Thanks for the explanations and the tip. Re:PS - No, this 1-element-list is just due to my shortening. I removed the surrounding loop to avoid noise and concentrate on the important parts of the question. But nontheless thanks for the pointer regarding the memory footprint. It's always good to keep an eye on that, esp. when doing stuff in Android. – Stefan Hoth Sep 18 '12 at 21:25
0

As @keyboardsurfer already pointed out, generics information is not available at runtime, so you should be also supplying the concrete class to the method to be able to instantiate it (you'll need it anyway to use it in gson.fromJson()).

To do it, just pass the Class of T, so you can reference T.class and make new T(). The following question has more details: Instantiating a generic class in Java.

For example (add try/catch clauses for newInstance() as needed) :

public static <T extends MyParentEntity> List<T> getFavList(Context context, Class<T> clazz) {

    String prefKey = buildKey( clazz.newInstance() );
    List<T> entityList = new ArrayList<T>();

    SharedPreferences settings = context.getSharedPreferences(prefKey, 0);

    GSON gson = new GSON();
    T entity = gson.fromJson(settings.getString(0, null), clazz);

    entityList.add(entity);

    return entityList;
}

The compiler will accept

List<MyEntity> list = getFavList(ctx, MyEntity.class);
List<MyAwesomeEntity> list = getFavList(ctx, MyAwesomeEntity.class);
Community
  • 1
  • 1
Xavi López
  • 27,550
  • 11
  • 97
  • 161