Передача класса с параметром типа в качестве параметра типа для общего метода в Java
Резюме проблемы:
Я хотел бы передать класс с параметром типа (например, ArrayList<SomeClass>
, например) в общий метод как параметр типа.
Скажем, у меня есть метод:
public static <T> T getGenericObjectFromJson(String json, Class<T> genericType){
// details unimportant, basically returns an object of specified type
return JsonParser.fromJson(json, genericType);
}
Этот метод, конечно, будет отлично работать для любого класса. Я могу вызвать такой метод, например:
getGenericObjectFromJson(jsonString, User.class)
Проблема: Я обнаружил, что не могу этого сделать:
getGenericObjectFromJson(jsonString, ArrayList<User>.class)
Синтаксически это явно недействительно. Однако я не уверен, как бы я мог добиться чего-то подобного. Я могу, конечно, пройти ArrayList.class
, однако добавление родового типа делает его более синтаксически недействительным, и я не могу думать об этом.
Единственное прямое решение было чем-то вроде этого (что кажется довольно глупым):
getGenericObjectFromJson(jsonString, new ArrayList<User>().getClass())
Однако мы все равно теряем общий тип и просто возвращаем ArrayList неизвестного типа (хотя его можно отличить). Кроме того, без необходимости создавать экземпляр объекта.
Мое единственное решение до сих пор заключалось в том, чтобы обернуть этот метод в классе, который содержит параметр универсального типа, который может быть создан таким образом:
public class JsonDeserializer<T>...
В этом случае метод getGenericObjectFromJson
будет использовать общий тип класса.
Вопрос (ы): В конечном счете мне любопытно, почему я не могу передать класс с параметром типа, и есть ли способ выполнить то, что я пытался сделать.
Как всегда, дайте мне знать, есть ли какие-либо проблемы с этим вопросом.
Ответы
Ответ 1
Это действительно возможно на Java, используя некоторые "трюки". Не поддавайтесь давлению со стороны фанатиков С#! (К/к)
"трюк" - это создать класс, который расширяет общий тип, и получить доступ к значению параметра типа родительского класса через Type
, возвращенный .getGenericSuperclass()
или .getGenericInterfaces()
.
Это довольно громоздко. Чтобы упростить нашу жизнь, Google уже написал большую часть скучной части кода для нас и предоставил ее через Guava.
Проверьте класс TypeToken
, который делает именно то, что вы хотите. Например:
TypeToken<List<String>> stringListTok = new TypeToken<List<String>>() {};
Затем вы передаете TypeToken<T>
вместо Class<T>
и все. Он предоставляет вам методы для отражения в типе, представленном T.
Что это делает внутри, просто вызывает .getClass().getGenericSuperclass()
(или ...Interfaces()
), затем некоторые уродливые отбрасывает от Type
до ParameterizedType
и извлекает всю информацию оттуда (.getActualTypeArguments()
и т.д.).
Наконец, если вы хотите сделать что-то подобное с Injection Dependency (т.е. предположим, что вам нужно ввести Class<T>
в конструктор класса или вы хотите получить экземпляр некоторого параметризованного интерфейса, в котором экземпляр должен зависеть от параметра типа), Google Guice (контейнер DI от Google) имеет очень похожий механизм для решения проблемы, называемый TypeLiteral
. Использование и код за кулисами почти идентичны TypeToken
из Guava. Проверьте это здесь: TypeLiteral