如何调用泛型对象的静态类方法?

7
我需要将泛型类型的类传递给一个类的构造函数。该类是来自RoboSpice Android库的SpiceRequest,请参考构造函数。似乎很奇怪该类要求在构造函数中传递泛型类型的类,因为它可以从泛型类型本身中访问,在这种情况下是RESULT.class,但也许我对此有所误解。无论如何,我不想改变库的代码,而是需要使用SpiceRequest的泛型类型的泛型类型Map<String,?extends Object>。以下是我的代码:
SpiceRequest<Map<String, ? extends Object>> request =
        new SpiceRequest<Map<String, ? extends Object>>(???) {
            ...
        };
< p > SpiceRequest构造函数的签名:

public SpiceRequest(final Class<RESULT> clazz) {
    ...
}

对于这个问题,我尝试过使用Map.class,但是编译器报了一个错误:The constructor SpiceRequest<Map<String,? extends Object>>(Class<Map>) is undefined.

Map<String, ? extends Object>.class会导致错误:Syntax error on tokens, PrimitiveType expected instead,特别是在? extends Object下面。它也会与Map.class产生同样的错误。

Map.<String, ? extends Object>class同样会导致编译器报错。

那么获得泛型类Class<Map<String, ? extends Object>>的正确方法是什么?


1
https://dev59.com/zWs05IYBdhLWcg3wAdWT - fscan
1
https://dev59.com/CnI-5IYBdhLWcg3wHUdB 也提供了相关信息。似乎在Java中除了显式向上转型再向下转型之外没有更为优雅的方法来实现这个功能。 - Jeff Lockhart
抱歉之前的回答不够清晰,我没有理解你想要做什么。 - sage88
4个回答

5
具体参数化类型和通配符参数化类型没有类字面量。根据Angelika Langer的泛型教程,通配符参数化类型在转换为字节码时会失去它们的类型参数,这个过程叫做类型擦除。作为类型擦除的一个副作用,所有泛型类型的实例共享相同的运行时表示形式,即对应的原始类型。换句话说,参数化类型没有自己的类型表示形式。因此,没有必要形成诸如 List<?>.classList<? extends Number>.classList<Long>.class 这样的类字面量,因为没有这样的Class对象存在。只有原始类型List具有代表其运行时类型的Class对象。它被称为List.class
出于同样的原因,在具体参数化类型中也没有类字面量,这是一个简洁的类型擦除
对于您的目的,您只需要对类字面量进行未经检查的转换即可。
Class<Map<String, ?>> c = (Class<Map<String, ?>>)(Class<?>)Map.class;

请注意,通过 Class<?> 进行双重转换是必要的,因为直接从 Class<Map> 转换为 Class<Map<String, ?>> 是非法的。

1
@gavenkoa 由于泛型不支持协变,所以将Class<Map>直接转换为Class<Map<String,?>>会导致编译错误。 - Paul Bellora

4

这个问题实际上与RoboSpice本身并不相关,而是Java语法的限制:在Java中,没有字面量可以用于获取参数化泛型类型的类/类型。

如果您想做类似于

public class ListTweetRequest extends SpiceRequest<List<Tweet>> {

   public ListTweetRequest(Object cacheKey ) {
       super( <Here is the problem>, cacheKey );
   }
}

如果你尝试将 List<Tweet>.class 传递给父级构造函数,那么你将无法通过该类。这实际上是Java的一种限制,因为泛型是使用类型擦除来实现的,List<Tweet>.class 在Java中没有真正的含义。

最好和最干净的解决方法是使用一个中间类型,例如:

public class ListTweet extends List<Tweet> {
}

public class ListTweetRequest extends SpiceRequest<ListTweet> {

   public ListTweetRequest(Object cacheKey ) {
       super( ListTweet.class, cacheKey );
   }
}

这种方法的好处是,它可以为你提供一个真正的类型,你可以将其传递给SpiceRequest的构造函数。但要注意混淆。在这些情况下,proguard会尝试删除ListTweet类,你必须明确地保留它以免被混淆。


0

我也遇到了这个问题,但我找到了像@Snicolas所说的解决方案。

请看这个robospice-sample-retrofit的示例:

https://github.com/octo-online/RoboSpice-samples/blob/release/robospice-sample-retrofit/src/com/octo/android/robospice/sample/retrofit/network/SampleRetrofitSpiceRequest.java

实际上最好不要从原始的List接口继承,而是从任何子类如ArrayList等继承,否则你将不得不实现所有接口方法。

import java.util.ArrayList;

public class Contributor {
    public String login;
    public int contributions;

    @SuppressWarnings("serial")
    public static class List extends ArrayList<Contributor> {
    }
}

你如何使用地图实现同样的事情? - Muhammad Alfaifi

0

我总是在猜测擦除,但你尝试过使用 Map.class 直接调用它吗?


是的,实际上那是我尝试的第一件事。我会更新问题并加入这些信息。 - Jeff Lockhart
不,这是编译器错误:“构造函数SpiceRequest<Map<String,?extends Object>>(Class <Map>)未定义”。 - Jeff Lockhart
我尝试了 Map<String, T>,但是出现了 T 无法解析为变量Map 无法解析为变量String 无法解析为变量 的错误。所以我感觉这种语法根本不被允许。我尝试的其他所有方法都只返回了 Map.class - Jeff Lockhart
是的,完全正确。与我上面的代码相同,但将所有的 Map<String, ? extends Object> 替换为 Map<String, Object> - Jeff Lockhart
嗯,你试过用HashMap了吗?也许问题出在接口上?至少你可以尝试 new HashMap<...>().getClass() :) - fscan
显示剩余5条评论

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