简体   繁体   中英

How to refactor a method to a generic one accepting only a subset of types?

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.

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;
    }

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.)

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

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);

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM