2009-12-24 3 views
13

Вот Java общая картина:Java общая функция: как вернуть Generic тип

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); 

Теперь один возвращается к необходимости слепки и подавляя предупреждение.

Я полагаю, что можно взять что-то из семейства java.lang.reflect.Type вместо Class, но это не особенно легко придумать. Это выглядит следующим образом:

class Dummy { 
Map<String, String> field; 
} 

... 

Type typeObject = Dummy.class.getField("field").getGenericType(); 

Учитывая это, вызываемая функция может извлечь базовый тип и использовать его для литья или newInstance-ков, но фиктивный бизнес-поле уверен, выглядит некрасиво.

Обратите внимание, что такие функции не всегда вызывают newInstance. Очевидно, что если они это сделают, им не нужно звонить resultClass.cast.

ответ

7

Вы не можете сделать это в стандартном Java API. Тем не менее, есть доступные классы полезности, которые могут получить Type из общего класса. Например, Google Gson (который преобразует JSON в полноправные Javabeans и наоборот), есть класс TypeToken. Вы можете использовать его следующим образом:

List<Person> persons = new Gson().fromJson(json, new TypeToken<List<Person>>() {}.getType()); 

Такая конструкция именно то, что вам нужно. Вы можете найти here источник sode, вы также можете найти его полезным. Это требует только дополнительного метода getResultData(), который может принять Type.

1

Вы можете использовать TypeLiteral, если хотите использовать классы классов типов. Они используются в Guice для обеспечения типов привязок. См. Исходный код Guice для получения дополнительных примеров и класс TypeLiteral.

Кстати, ou не нужно делать, если вы звоните, например, resultClass.newInstance().

public <T> T getResultData(Class<T> resultClass, other_args) {  
    ... 
    return resultClass.newInstance(); 
} 
+2

За исключением того, что вы не можете получить и таким образом передать экземпляр класса для универсального класса, такого как «Map », поэтому это не поможет вообще с вопросом OP. – ChssPly76

+0

Yup. Именно так мы и пришли. – bmargulies

+0

Они не поддерживаются на языке, но вы используете TypeLiteral, изобретенный Нилом Гафтером. http://gafter.blogspot.com/2006/12/type-literals.html –

1

Так что, если я правильно понял, что вы на самом деле пытаетесь сделать это (незаконное псевдо-синтаксис):

public <T, S, V> T<S, V> getResultData(Class<T<S, V>> resultClass, other_args) { 
    ... 
    return resultClass.cast(T-thing); 
} 

Вы не можете, потому что вы не можете в дальнейшем параметризировать универсального типа T. Мессинга о с java.lang.reflect.* не поможет:

  1. Там нет такого понятия, как Class<Map<String, String>> и, таким образом, нет возможности динамически создавать его экземпляр.
  2. Там нет никакого способа фактически объявить метод в вопросе как возвращение T<S, V>
+0

@ ChssPly76: Как это бывает, существует объект GenericType для Map . Если у вас есть поле типа Map , и вы вызываете Field.getGenericType, вы получите его. Однако, единственный способ приобрести один из них - это, действительно, получить его от отражения от поля или типа возврата. – bmargulies

+0

Вы имеете в виду 'ParameterizedType'.Да, есть и да, вы можете получить его из объявления поля/метода. И нет, это не ** то же, что и «Класс», и поэтому ** не поможет вам каким-либо образом: вы не можете использовать его, и вы не можете создать экземпляр соответствующего класса, используя только ' ParameterizedType'. – ChssPly76

+0

Ну, на самом деле, это не так сложно, как кажется. Вызываемая функция может вызвать getRawType() для параметра ParameterizedType для newInstance/cast. Это просто уродливо, но похоже, что это так хорошо, как получается. – bmargulies

1

Ну ,. вот уродливое (частичное) решение. Вы не можете обобщенно paramaterize родовых пар, так что вы не можете писать

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, если он существует в подписи класса.

+0

Интересно, и он будет работать и для двух параметров (путем расширения «Карты»). Но, честно говоря, выбирая между этим и '@ SuppressWarnings', я беру последний каждый раз :-) – ChssPly76

+1

Я согласен - я сказал« уродливый »;) –

1

Да, я не думаю, что это возможно.

Представьте себе этот сценарий. У вас есть файл с сериализованным объектом, который вы хотите прочитать. Если вы сделаете функцию общей, вы сможете использовать ее без броска. Но скажем, у вас есть карта, сериализованная в файл.

Когда вы десериализуете его, есть , нет способа узнать, каков был исходный тип общей карты. Что касается java, это всего лишь карта

Я столкнулся с этим со списками, и это было неприятно. Но то, что я на самом деле закончил, это создание общего класса «конвертирующей коллекции», который берет коллекцию и «конвертер» (который может быть анонимным внутренним типом).

Затем, когда вы перебираете конвертирующую коллекцию, каждый элемент получает бросок. И это решило проблему предупреждения. Очень много работы, чтобы избавиться от предупреждения, но я не думаю, что это была основная причина, по которой я придумал эту структуру. Вы также можете сделать более мощные вещи, например, взять коллекцию имен файлов, а затем написать конвертер, который загружает данные в файл, или делает запрос, или что-то еще.

1

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

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) { ... } 

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

Смежные вопросы