Java generic function: как вернуть общий тип
Здесь общий шаблон Java:
public <T> T getResultData(Class<T> resultClass, other_args) {
...
return resultClass.cast(T-thing);
}
Типичный вызов выглядит следующим образом:
DoubleBuffer buffer;
buffer = thing.getResultData(DoubleBuffer.class, args);
Я никогда не мог понять, как использовать этот шаблон, когда желаемый тип возвращаемого значения сам по себе является общим. Чтобы быть "конкретным", что, если такая функция хочет вернуть Map<String,String>
? Так как вы не можете получить объект класса для общего, конечно, единственным вариантом будет передать Map.class
, а затем вам понадобится листинг и @SuppressWarning
.
В конце появляется вызов типа:
Map<String, String> returnedMap;
returnedMap = thing.getResultData(Map.class, some_other_args);
Теперь один возвращается к необходимости приведения и пресечения предупреждения.
Я полагаю, что вместо Class
можно взять что-то из семейства java.lang.reflect.Type
, но это не особенно легко придумать. Это выглядит так:
class Dummy {
Map<String, String> field;
}
...
Type typeObject = Dummy.class.getField("field").getGenericType();
Учитывая это, вызываемая функция может извлечь базовый тип и использовать это для кастинга или newInstance-ing, но фиктивный бизнес на поле выглядит безобразным.
Обратите внимание, что такие функции не всегда вызывают newInstance
. Очевидно, что если это так, им не нужно называть resultClass.cast
.
Ответы
Ответ 1
Вы не можете сделать это в стандартном Java API. Тем не менее, есть доступные классы полезности, которые могут получить Type
из общего класса. Например, в Google Gson (который преобразует JSON в полноправные Javabeans и наоборот) существует класс TypeToken
. Вы можете использовать его следующим образом:
List<Person> persons = new Gson().fromJson(json, new TypeToken<List<Person>>() {}.getType());
Такая конструкция именно то, что вам нужно. Вы можете найти здесь источник sode, вы также можете найти его полезным. Это требует только дополнительного метода getResultData()
, который может принять Type
.
Ответ 2
Вы можете использовать TypeLiteral, если вы хотите использовать классы контейнеров типов. Они используются в Guice для обеспечения привязок типов. См. Исходный код Guice для получения дополнительных примеров и TypeLiteral class.
Кстати, ou не нужно бросать, если вы вызываете resultClass.newInstance()
, например.
public <T> T getResultData(Class<T> resultClass, other_args) {
...
return resultClass.newInstance();
}
Ответ 3
Итак, если я правильно понял, что вы действительно пытаетесь сделать (незаконный псевдосинтакс):
public <T, S, V> T<S, V> getResultData(Class<T<S, V>> resultClass, other_args) {
...
return resultClass.cast(T-thing);
}
Вы не можете, потому что не можете далее параметризовать общий тип T
. Беспорядок с помощью java.lang.reflect.*
не поможет:
- Нет такой вещи, как
Class<Map<String, String>>
и, следовательно, нет возможности динамически создавать свой экземпляр.
- Невозможно на самом деле объявить рассматриваемый метод как возвращающий
T<S, V>
Ответ 4
Ну,. здесь уродливое (частичное) решение. Вы не можете в общих чертах параметризировать общий параметр, поэтому вы не можете писать
public <T<U,V>> T getResultData ...
но вы можете вернуть параметризованную коллекцию, например. для множества
public < T extends Set< U >, U > T getResultData( Class< T > resultClass, Class< U > paramClass ) throws IllegalAccessException, InstantiationException {
return resultClass.newInstance();
}
где вы могли бы избавиться от U-param, если он существовал в подписи класса.
Ответ 5
Да, я не думаю, что это возможно.
Представьте себе этот сценарий. У вас есть файл с сериализованным объектом, который вы хотите прочитать. Если вы сделаете функцию общей, вы сможете использовать ее без броска. Но скажем, у вас есть карта, сериализованная в файл.
Когда вы десериализуете его, нет способа узнать, каков был исходный тип общей карты. Что касается java, это всего лишь карта
Я столкнулся с этим со списками, и это было неприятно. Но то, что я на самом деле закончил, это создание общего класса "конвертирующей коллекции", который берет коллекцию и "конвертер" (который может быть анонимным внутренним типом).
Затем, когда вы перебираете конвертирующую коллекцию, каждый элемент получает бросок. И это решило проблему предупреждения. Это большая работа, чтобы избавиться от предупреждения, но я не думаю, что это была основная причина, по которой я придумал эту структуру. Вы также можете сделать более мощные вещи, например, взять коллекцию имен файлов, а затем написать конвертер, который загружает данные в файл, или делает запрос, или что-то еще.
Ответ 6
Если вам действительно не нужен точный общий тип карты, и ваш код не полагается на этот тип, но вас просто раздражают предупреждения, которые вы могли бы написать:
Map<?, ?> returnedMap;
returnedMap = thing.getResultData(Map.class, some_other_args);
то вы можете использовать любой из методов карты, которые не параметризуются параметром типа containsKey, clear, iterator и т.д., или перебирают по entrySet или передают returnMap любому универсальному методу, и все будет безопасным по типу.
Map<?, ?> returnedMap;
returnedMap = thing.getResultData(Map.class, some_other_args);
Object myKey = ...;
Object someValue = returnedMap.get(myKey);
for (Iterator<? extends Map.Entry<?,?>> it = returnedMap.entrySet().iterator(); it.hasNext(); )
if (it.next().equals(myKey))
it.remove();
for (Map.Entry<?,?> e : returnedMap.entrySet()) {
if (e.getKey().equals(myKey))
e.setValue(null); // if the map supports null values
}
doSomething(returnedMap);
...
<K,V> void doSomething(Map<K,V> map) { ... }
на самом деле существует много вещей, которые вы можете сделать с помощью карты безопасным образом, не зная ее ключ или тип значения.