Подтвердить что ты не робот

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.

4b9b3361

Ответ 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) { ... }

на самом деле существует много вещей, которые вы можете сделать с помощью карты безопасным образом, не зная ее ключ или тип значения.