如何创建一个已知类型的类字面值:Class <List<String>>

72

请看以下示例:

public Class<List<String>> getObjectType() {
    // what can I return here?
}
我应该返回哪个类字面表达式才能满足泛型并通过编译?List.class会报错,List.<String>class也不行。如果你想知道“为什么”,我正在编写Spring的FactoryBean<List<String>>实现,这要求我实现Class<List<String>> getObjectType()方法。然而,这不是一个关于Spring的问题。编辑: SpringSource的大佬们已经听到了我的哀求,所以Spring 3.0.1将把getObjectType()方法的返回类型更改为Class<?>,从而巧妙地避免了这个问题。
9个回答

62

您可以根据需要进行转换,就像这样

return (Class<List<String>>) new ArrayList<String>().getClass();

或者

return (Class<List<String>>) Collections.<String>emptyList().getClass();

但我想你不是想要那样的。虽然它能够工作,但带有警告,并不完美。

我刚发现了这个:

为什么通配符参数化类型没有类字面量?

因为通配符参数化类型没有确切的运行时类型表示。

所以强制转换可能是唯一的方法。


1
你在哪里找到那个引用的? - skaffman
你的第一个例子在我的环境中(SE 1.6)无法工作,只有第二个例子可以。 将ArrayList实例化为一个变量并返回myVar.getClass()可以解决问题。找到:java.lang.Class<capture#972 of ? extends java.util.ArrayList>需要:java.lang.Class<java.util.List<com.foo.Bar>> - user

17
您永远不应该使用结构体Class<List<String>>。这是毫无意义的,在Java中应该会产生警告(但没有)。类实例始终表示原始类型,因此您可以使用Class<List>;仅此而已。如果您想要表示像List<String>这样的具有复合泛型类型的东西,则需要像Guice使用的“超类型令牌”: http://google-guice.googlecode.com/git/javadoc/com/google/inject/TypeLiteral.html

1
我同意这不是一个好主意,但Java允许它,如果你试图实现一个接口,可能别无选择,所以我们仍然需要一种(半)优雅的方法来做到这一点。 - skaffman

14

您可以像这样实现该方法:

public Class<List<String>> getObjectType() {
    return (Class<List<String>>) ((Class)List.class);
}

5
我喜欢这种方式是因为你不需要创建新实例。 - Martin Forte
3
外部转换是不必要的。你可以这样写:return (Class)List.class;,我觉得更容易阅读。 - TXN

9
存在一个Class<List<String>>是具有潜在危险的。以下是原因:
// This statement generates a warning - for a reason...
Class<List<String>> unsafeListClass = (Class<List<String>>) (Class<?>) List.class;

List<Integer> integerList = new ArrayList<Integer>(); // Ok
integerList.add(42); // Ok

System.out.println(unsafeListClass.isInstance(integerList)); // Prints "true".
List<String> stringList =
   unsafeListClass.cast(integerList); // Succeeds, with no warning!
stringList.add("Hello, World!"); // Also succeeds with no warning

for (int x: integerList) {
    // Compiles without warning, but throws ClassCastException at runtime
    System.out.println(100-x);
}

1
这是一个奇怪的例子。由于泛型在运行时不被实体化,因此运行时转换在许多情况下绕过了泛型类型系统。泛型主要是一种编译时构造,它允许您进一步约束类类型的类型。如果您能保留泛型签名,那将非常有用,因为只要您不进行强制转换,就可以使大量代码更易读且类型安全。 - Peter Kriens

7

在springframework.org上发现了这个链接,它提供了一些见解。

例如:

List<String> myList = new ArrayList<String>();
return (Class<List<String>>)myList.getClass();

@Bozho:springframework的链接有一个不同的方法签名java.lang.Class<? extends T> getObjectType(). 我已经更新了我的答案以反映OP问题的签名。 - JRL

2

答案中的第一个链接已经过时。 - undefined

1

我不确定这是否可能,因为任何类字面值都将被编译为Class.forName(...),并且由于这发生在运行时,因此没有通用信息留下。


0
这个怎么样?
public class TestMain {
    public static void main(String[] args) throws Exception {
        Type type = TestMain.class.getMethod("dummy").getGenericReturnType();
        System.out.println("type = " + type);
    }

    public List<Integer> dummy() {return null;}
}

这将打印:

type = java.util.List<java.lang.Integer>

0
以下方法存在问题:
> public Class<List<String>> getModelType() {
>   return (Class<List<String>>) new ArrayList<String>().getClass();
> }

例如,如果您想测试一个对象是否为某种类型

org.eclipse.emf.common.util.BasicEList<String> 

的类型为

List<String> 

根据前面提到的getModelType()方法返回的结果,例如:

BasicEList<String> fromObject = ...;
if (getModelType().isAssignableFrom(fromObject.getClass())) {
    transferFromModelToUi(getModelType().cast(fromObject));
}

它将返回false,而实际上应该是true,因为两个对象都实现了List接口(由于getModelType()返回类型为List而不是ArrayList的Class对象)。

这里是我的解决方法(有点麻烦,但在上面的示例中可以得到正确的结果,可以移到静态初始化程序中):

public Class<List<String>> getModelType() {
    Class<?> arrayListClass = new ArrayList<String>().getClass();
    Class<?>[] interfaces = arrayListClass.getInterfaces();
    int index = 0;
    for (int i = 0; i < interfaces.length; i++) {
        if (interfaces[i].equals(List.class)) {
            index = i;
            break;
        }
    }
    return (Class<List<String>>) interfaces[index];
}

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