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

15

以下是一个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。显然,如果他们这样做,他们就不需要调用resultClass.cast。
6个回答

8
在标准的Java API中无法实现这一点。但是,有可用的实用程序类可以从通用类中获取Type。例如,在Google Gson(它将JSON转换为完整的Javabeans和反之亦然)中有一个TypeToken类。您可以按照以下方式使用它:
List<Person> persons = new Gson().fromJson(json, new TypeToken<List<Person>>() {}.getType());

这样的结构正是你所需要的。您可以在这里找到源代码,您可能也会发现它很有用。它只需要一个额外的getResultData()方法,该方法可以接受一个Type

2

好的,这是一个丑陋的(部分)解决方案。你不能一般性地对一个泛型参数进行参数化,因此你不能写

public <T<U,V>> T getResultData ...

但是你可以返回一个带有参数的集合,例如对于一个set集合

public < T extends Set< U >, U > T getResultData( Class< T > resultClass, Class< U > paramClass ) throws IllegalAccessException, InstantiationException {
        return resultClass.newInstance();
    }

如果类签名中存在U-param,则可以通过以下方式摆脱它。


很有意思,而且它也可以对两个参数起作用(通过扩展“Map”)。但是,老实说,在这个和“@SuppressWarnings”之间做选择的话,我每次都会选择后者 :-) - ChssPly76
1
我同意 - 我确实说过“丑陋” ;) - Steve B.

2

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

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

当您反序列化它时,无法知道原始通用映射类型是什么。就Java而言,它只是一个Map。

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

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


1

如果您不需要地图的确切通用类型,而且您的代码也不依赖于该类型,但您只是因为警告而感到烦恼,那么您可以编写:

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

然后,您可以使用任何未进行类型参数化的映射方法,例如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) { ... }

实际上,您可以以类型安全的方式执行许多操作,而无需知道Map的键或值类型。


1

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

顺便说一下,如果您调用resultClass.newInstance(),则不需要进行转换。

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

2
除非您无法获取并因此传递泛型类的Class实例,例如Map<String, String>,否则这对OP的问题没有任何帮助。 - ChssPly76
是的,这正是我们到达这里的方式。 - bmargulies
它们在该语言中不受支持,但您可以使用Neal Gafter发明的TypeLiteral。http://gafter.blogspot.com/2006/12/type-literals.html - Chandra Patni
如果你把TypeLiteral的引用编辑到你的实际答案中,我会给它点赞。 - bmargulies

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>

@ChssPly76:恰好,Map<String, String> 有一个GenericType对象。如果您有一个类型为Map<String, String>的字段,并调用Field.getGenericType,则会得到它。然而,获取其中之一的唯一方法是通过对字段或返回类型进行反射。 - bmargulies
你指的是 ParameterizedType。是的,它存在,并且可以从字段/方法声明中获取。但不,它与Class不同,因此不会以任何方式帮助您:您无法将其强制转换,也不能仅使用ParameterizedType创建相应类的实例。 - ChssPly76
实际上,这并不像看起来的那样不可能。被调用的函数可以在ParameterizedType上调用getRawType()以进行newInstance / cast目的。它只是丑陋的,但看起来这就是最好的方法。 - bmargulies

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接