Java泛型函数:如何返回泛型类型

时间:2009-12-24 16:58:51

标签: java generics

这是一个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);

现在又回到了需要强制转换并抑制警告的情况。

我认为可以从java.lang.reflect.Type家族而不是Class获取一些东西,但这些并不是特别容易编造的。看起来像是:

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

...

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

鉴于此,被调用函数可以提取基类型并将其用于转换或newInstance,但虚拟字段业务肯定看起来很难看。

请注意,像这样的函数并不总是调用newInstance。显然,如果他们这样做,他们就不需要拨打resultClass.cast

6 个答案:

答案 0 :(得分: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 :(得分:2)

好,.这是一个丑陋的(部分)解决方案。你不能一般地使用通用参数,所以你不能写

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。

答案 2 :(得分:2)

是的,我不认为这是可能的。

想象一下这种情况。您有一个文件,其中包含您想要读取的序列化对象。如果你使函数通用,你将能够在没有强制转换的情况下使用它。但是,假设您将Map序列化为文件。

当你反序列化它时,无法知道原始通用地图类型是什么。就java而言,它只是一个Map

我用列表遇到了这个,这很烦人。但我实际上最终做的是创建一个通用的“转换集合”类,它采用集合和“转换器”(可以是匿名内部类型)。

然后,当您遍历转换集合时,每个项目都会被转换。这解决了警告问题。除了警告之外,还有很多工作要做,但我不认为这是我提出这个框架的主要原因。您还可以执行更强大的操作,例如获取文件名集合,然后编写一个转换器来加载文件中的数据,或进行查询等等。

答案 3 :(得分:1)

如果要使用类型安全容器类,可以使用TypeLiteral。它们在Guice中用于提供类型安全绑定。有关更多示例和TypeLiteral类,请参阅Guice源代码。

顺便说一句,如果你打电话给resultClass.newInstance(),你不需要演员表。

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

答案 4 :(得分: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>

答案 5 :(得分:1)

如果你真的不需要地图的确切泛型类型,而你的代码不依赖于那种类型,但你只是被你可以写的警告所困扰:

Map<?, ?> returnedMap;
returnedMap = thing.getResultData(Map.class, some_other_args);

然后你可以使用任何非类型参数化的map方法,如containsKey,clear,iterator等,或者遍历entrySet或将returnedMap传递给任何泛型方法,一切都是类型安全的。

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) { ... }
实际上,在不知道其键或值类型的情况下,您可以使用类型安全的方式执行许多操作。