The short answer is that you need to move the creation of the TypeToken
out of Executor
, bind the T
in Response<T>
when you create the token (new TypeToken<Response<Language>>() {}
), and pass in the type token to the Executor
constructor.
The long answer is:
Generics on a type are typically erased at runtime, except when the type is compiled with the generic parameter bound. In that case, the compiler inserts the generic type information into the compiled class. In other cases, that is not possible.
So for instance, consider:
List<Integer> foo = new ArrayList<Integer>();
class IntegerList extends ArrayList<Integer> { ... }
List<Integer> bar = new IntegerList();
At runtime, Java knows bar
contains integers because the Integer
type is bound to ArrayList
at compile time, so the generic type information is saved in the IntegerList
class file. However, the generic type information for foo
is erased, so at runtime it is not really possible to determine that foo
is supposed to contain Integer
s.
So it often comes up that we need generic type information in a situation where it normally would be erased before runtime, such as here in the case of parsing JSON data in GSON. In these situations, we can take advantage of the fact that type information is preserved when it is bound at compile-time (as in the IntegerList
example above) by using type tokens, which are really just tiny anonymous classes that conveniently store generic type information.
Now to your code:
Type responseType = new TypeToken<Response<T>>() {}.getType();
In this line of your Executor
class, we create an anonymous class (inheriting from TypeToken
) which has the type Response<T>
hard coded (bound) at compile-time. So at runtime, GSON is able to determine that you want an object of Response<T>
. But it doesn't know what T
is, because you didn't specify it at compile-time! So GSON cannot determine what type will be in the List
of the Response
object it creates, and it just creates a StringMap
instead.
The moral of the story is that you need to specify that T
at compile-time. If Executor
is meant to be used generically, you probably need to create the type token outside of that class, in your client code. Something like:
class Executor<T> {
private TypeToken<Response<T>> responseType;
private Response<T> response;
public Executor(TypeToken<Response<T>> responseType) {
this.responseType = responseType;
}
public void execute() {
this.response = new Gson().fromJson(json, responseType.getType());
}
public Response<T> getResponse() { return this.response; }
}
// client code:
Executor<Language> executor = new Executor<Language>(new TypeToken<Response<Language>>() { });
executor.execute();
List<Language> languages = executor.getResponse().getData();
System.out.println(languages.get(0).alias); // prints "be"
By the way, I did test the above on my machine.
Sorry if that was too long!