Krótka odpowiedź jest taka, że trzeba przenieść utworzenie TypeToken
z Executor
, wiążą T
w Response<T>
podczas tworzenia tokena (new TypeToken<Response<Language>>() {}
) i przechodzą w typie tokena do konstruktora Executor
.
Długa odpowiedź brzmi:
Generics od typu są zazwyczaj usuwane w czasie wykonywania, chyba że typ jest skompilowany z parametru rodzajowego związana. W takim przypadku kompilator wstawia ogólną informację o typie do skompilowanej klasy. W innych przypadkach nie jest to możliwe.
Tak na przykład, należy rozważyć:
List<Integer> foo = new ArrayList<Integer>();
class IntegerList extends ArrayList<Integer> { ... }
List<Integer> bar = new IntegerList();
W czasie wykonywania Java wiebar
zawiera liczby całkowite, ponieważ typ Integer
jest zobowiązany do ArrayList
w czasie kompilacji, więc ogólne informacje typu jest zapisywany w klasie IntegerList
plik. Jednak ogólna informacja o typie dla foo
jest wymazana, więc w czasie rzeczywistym nie jest możliwe stwierdzenie, że foo
ma zawierać Integer
s.
Często pojawia się potrzeba generycznych informacji o typie w sytuacji, w której normalnie zostałaby wymazana przed uruchomieniem, na przykład tutaj w przypadku analizy danych JSON w GSON.W takich sytuacjach możemy wykorzystać fakt, że informacje o typie są zachowywane, gdy są powiązane w czasie kompilacji (jak w powyższym przykładzie IntegerList
) za pomocą type tokens, które są tak naprawdę małymi anonimowymi klasami, które wygodnie przechowują ogólne informacje o typie.
Teraz do kodu:
Type responseType = new TypeToken<Response<T>>() {}.getType();
W tej linii swojej klasie Executor
tworzymy klasę anonimowy (dziedziczenie z TypeToken
), która ma typ Response<T>
zakodowanego (związany) w czasie kompilacji. Tak więc w czasie wykonywania GSON może ustalić, czy chcesz obiekt o numerze Response<T>
. Ale nie wie, co to jest T
, ponieważ nie określiłeś go podczas kompilacji! Zatem GSON nie może określić, jaki typ będzie w obiekcie List
tworzonego obiektu, a zamiast tego tworzy po prostu StringMap
.
Morał z tej historii polega na tym, że podczas kompilacji trzeba określić, że T
. Jeśli zamierzamy używać generycznie Executor
, prawdopodobnie trzeba utworzyć token typu poza tą klasą w kodzie klienta. Coś takiego:
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"
Przy okazji, testowałem powyższe na mojej maszynie.
Przepraszam, jeśli to było za długie!
Czy na pewno występuje wyjątek w tym wierszu? Nie widzę, gdzie wywoływane jest wywołanie execute() w przykładowym kodzie. –
@SamuelEUSTACHI Och, mój zły. Pytanie edytowane, wymknęło się, gdy wkleiłem kod (jest nieco uproszczony). Tak, jestem w 100% pewien, że występuje dokładnie w tej linii (co najmniej zgodnie ze stosem stacków). – Zar
@Zar executor = nowy executor
Visruth