【问题标题】:Unable to put Object in a generic Map无法将对象放入通用地图
【发布时间】:2018-01-15 05:51:50
【问题描述】:

我已经声明了一个 Map,其中包含 Long 类型的键到嵌套 Map 的映射

嵌套映射具有 Class 类型的键和 SomeClient 类型的值 基本上我正在尝试生成类类型到产生类类型响应的客户端的映射。

private static final Map<Long, Map<Class<? super GenericResponse>, SomeClient<? super GenericResponse>>> someClientMap 
       = new HashMap<Long, Map<Class<? super GenericResponse>, SomeClient<? super GenericResponse>>>(); // <? super GenericResponse> should enable me to put subclasses of type GenericResponse in the Map

getSomeClient 方法是一个泛型方法,它接受类 Type 作为参数,并返回产生 Class Type 响应的客户端。(类类型只能是 GenericResponse 的子类:注意方法签名中的 Type 参数)

public static <T extends GenericResponse> SomeClient<T> getSomeClient(long clientId,Class<T> clazz) throws IOException {
    if (someClientMap.get(clientId) == null) {
        synchronized (someClientMap) {
            someClientMap.put(clientId,  new HashMap<Class<T>,SomeClient<T>>()); //getting error here                       
        }
    }
    return someClientMap.get(clientId);
}

问题是我在尝试将客户端放入地图时遇到编译时错误。

确切的错误是

The method put(Long, Map<Class<? super GenericResponse>,SomeClient<? super GenericResponse>>) in the type Map<Long,Map<Class<? super GenericResponse>,SomeClient<? super GenericResponse>>> is not applicable for the arguments (long, HashMap<Class<T>,SomeClient<T>>)

我很难指出我到底做错了什么。请帮忙。

SomeClient 的声明是

public class SomeClient<T extends GenericResponse>

对于构造函数是

public SomeClient(Class<T> clazz) 

【问题讨论】:

  • 看起来问题出在Long vs long - 你把long 参数,地图期望Long。添加强制转换或使用Long.valueOf(.) 方法
  • 你能在问题中发布SomeClientGenericResponse的声明吗?

标签: java generics hashmap extends super


【解决方案1】:

您的代码中有几个问题。首先,如果你想允许子类型,你应该使用extends 而不是super

private static final Map<Long, Map<Class<? extends GenericResponse>, SomeClient<? extends GenericResponse>>> someClientMap 
       = new HashMap<Long, Map<Class<? extends GenericResponse>, SomeClient<? extends GenericResponse>>>();

这表明您允许GenericResponse 的子类型作为ClassSomeClientMap 中的通用参数,但它不允许您使用Class 的子类型(@987654330 也是如此@) 本身,据我们所知,Class&lt;T&gt;(其中T extends GenericResponse)是Class&lt;? extends GenericResponse&gt; 的子类型,因为前者是后者针对特定T 的“特化”。

使用该定义,您必须在将内部Map 放入外部时保持相同的通用参数:

someClientMap.put(clientId, new HashMap<Class<? extends GenericResponse>, SomeClient<? extends GenericResponse>>());

你可以保留put,如果你只是在初始声明中添加更多extends以允许ClassSomeClient的子类型:

private static final Map<Long, Map<? extends Class<? extends GenericResponse>, ? extends SomeClient<? extends GenericResponse>>> someClientMap
        = new HashMap<Long, Map<? extends Class<? extends GenericResponse>, ? extends SomeClient<? extends GenericResponse>>>();

然后你可以这样做:

someClientMap.put(clientId, new HashMap<Class<T>, SomeClient<T>>());

无论哪种方式,访问someClientMap时,您都不会摆脱丑陋的演员阵容:

return (SomeClient<T>) someClientMap.get(clientId).get(clazz);

【讨论】:

  • 感谢您的回答,但我使用了 super 而不是 extends,因为我想将 GenericType 类的不同子类添加到同一个映射中。请参考这个问题stackoverflow.com/questions/4343202/… 进行解释。
  • @rohit.agrawal 您是否阅读过该问题的答案?这很简单:如果你想允许 GenericType 的 子类 使用 ? extends GenericType;如果您想允许 GenericType 的 超类,请使用 ? super GenericType
  • 我不只是想在地图中允许 GenericType 的一个子类,我想在同一个地图中允许多个子类型。在此处的链接中复制部分答案以强调我在说什么。如果一个列表被声明为 List&lt;? extends Number&gt; foo3 ;您不能添加整数,因为 foo3 可能指向 List。您不能添加 Double,因为 foo3 可能指向 List。您不能添加数字,因为 foo3 可能指向 List.
  • 在该列表示例中,如果您改用List&lt;? super Number&gt;,您真的可以很高兴地添加整数和双精度数。问题是当你试图得到任何东西时,因为你只知道它是一个Object;你不能确定它们是数字,因为列表可以是数字的任何超类。在该示例中,您希望避免一起使用通配符并使用普通的List&lt;Number&gt;,您可以在其中愉快地添加整数和双精度数,并且当您得到任何东西时,至少您知道它是一个数字。
  • 我的第一个建议仍然是您应该使用的,因为我的 extends 在地图内的类中,而不是在容器本身中,在您的列表示例和我的第二个示例中就是这种情况建议。
【解决方案2】:

该错误是由于 Java 泛型是不变,即尽管StringObject 的子类型,但以下行将无法编译:

Map<Long, Object> map = new HashMap<Long, String>();

因为Map&lt;Long, String&gt; 不是Map&lt;Long, Object&gt; 的子类型。

但是我们可以通过使用有界通配符来克服这个限制,即

Map<Long, ? extends Object> map = new HashMap<Long, String>();

这就是为什么你的代码中有一个Class&lt;? super GenericResponse&gt;,但你在中途停了下来。你应该在下一个更高级别使用相同的技术,即写:

Map<Long, Map<? extends Class<? extends GenericResponse>, ? extends SomeClient<? extends GenericResponse>>>

而不是

Map<Long, Map<Class<? extends GenericResponse>, SomeClient<? extends GenericResponse>>>

顺便说一句,我在示例中将 ? super 替换为 ? extends,因为我认为您没有正确使用它们。

【讨论】:

    【解决方案3】:

    我发现了这个问题。在下面的声明中

    someClientMap.put(clientId,  new HashMap<Class<T>,SomeClient<T>>());
    

    我正在创建的新 HashMap 比外部映射接受的限制更多。因为 T 在泛型方法中扩展了 GenericResponse。

    该声明允许将 GenericResponse 类的不同子类存储在同一个映射中,因为我使用的是 super

    private static final Map<Long, Map<Class<? super GenericResponse>, SomeClient<? super GenericResponse>>> someClientMap 
       = new HashMap<Long, Map<Class<? super GenericResponse>, SomeClient<? super GenericResponse>>>(); // <? super GenericResponse> should enable me to put subclasses of type GenericResponse in the Map
    

    请参考Difference between <? super T> and <? extends T> in Java 以获得很好的解释。

    为了解决这个问题,我将 put 语句替换为

    someClientMap.put(clientId,  new HashMap<Class<? super GenericResponse>,SomeClient<? super GenericResponse>>());
    

    这个 put 确保内部映射确实可以保存 GenericResponse 类的不同子类的实例,如初始声明中所声明的那样。

     private static final Map<Long, Map<Class<? super GenericResponse>, SomeClient<? super GenericResponse>>> someClientMap 
       = new HashMap<Long, Map<Class<? super GenericResponse>, SomeClient<? super GenericResponse>>>();
    

    【讨论】:

    • 您使用 ? super GenericResponse 但在文本中您说您需要“保存 GenericResponse 类的不同 子类 的实例” - 这很令人困惑,要么使用? super GenericResponse 并说“GenericResponse 类的超类”或使用 ? extends GenericResponse 并说“GenericResponse 类的子类”
    • 当我使用 super GenericResponse> 并说“保存 GenericResponse 类的不同子类的实例”我是认真的。这有点违反直觉,但事实就是如此。请参考stackoverflow.com/questions/4343202/… 的公认答案以获得很好的解释。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多