Java泛型:(深度复制)泛型映射的方法签名

6

我有几个Map,它们本身可能包含任何类型的Map。我编写了一个带有以下签名的方法:

public static <K,V> HashMap<K,V> deepCopyHashMap(HashMap<K,V> s);

然而,我现在希望将这段代码概括为支持一般的Map,但仍返回与参数相同类型的对象。因此,代码不再是:

public static <K,V> HashMap<K,V> deepCopyHashMap(HashMap<K,V> s);
public static <K,V> CheckedMap<K,V> deepCopyCheckedMap(CheckedMap<K,V> s);
public static <K,V> TreeMap<K,V> deepCopyTreeMap(TreeMap<K,V> s);
...
etc.

我希望有类似这样的内容:

我希望能拥有这样的东西:

public static <K,V, M extends Map<K,V>> M<K,V> deepCopyMap(M<K,V> s);

然而,这给了我:
Multiple markers at this line
- The type M is not generic; it cannot be parameterized with arguments <K, 
 V>
- The type M is not generic; it cannot be parameterized with arguments <K, 
 V>

如何正确声明方法签名并返回正确类型的对象(不使用内部反射)?
对于这个项目,增加更多依赖项真的不是一个选项,因此我更喜欢不依赖外部库的解决方案。同时,我已经研究了Cloneable接口,但它只是一个标记接口(对于Map来说没有实现),对我没什么用处。
编辑: 为了参考,这是我深度复制嵌套HashMap的代码(代码可以正常工作):
public static <K,V> HashMap<K,V> deepCopyHashMap(HashMap<K,V> source){
    HashMap<K,V> result = new HashMap<K, V>();
    for(Map.Entry<K, V> entry : source.entrySet()){
        K k = entry.getKey();
        V v = entry.getValue();
        if(k instanceof HashMap<?,?>){
            k = (K) deepCopyHashMap((HashMap<?,?>) k);
        }
        if(v instanceof HashMap<?,?>){
            v = (V) deepCopyHashMap((HashMap<?,?>) v);
        }
        result.put(k, v);
    }
    return result;
}

编辑:解决方案

  1. This is not an ideal solution.. It will fail if the there is no default constructor for the runtime type of the nested Map. I have tested it with nested HashMaps and the runtime type is correctly copied.

    @SuppressWarnings("unchecked")
    public static <K,V, M extends Map<K,V>> M deepCopyMap(M source) throws InstantiationException, IllegalAccessException{
        M result = (M) source.getClass().newInstance();
        for(Map.Entry<K, V> entry : source.entrySet()){
            K k = entry.getKey();
            V v = entry.getValue();
            if(k instanceof Map<?,?>){
                k = (K) deepCopyMap((Map<?,?>) k);
            }
            if(v instanceof Map<?,?>){
                v = (V) deepCopyMap((Map<?,?>) v);
            }
            result.put(k, v);
        }
        return result;
    }
    
  2. This is much safer, but all the known types need to be listed explicitly:

    @SuppressWarnings("unchecked")
    public static <K,V, M extends Map<K,V>> M deepCopyMap(M source){
        M result;
        if(source instanceof HashMap){
            result = (M) new HashMap<K,V>();
        } else {
            //fail
        }
        // etc. add more types here
        for(Map.Entry<K, V> entry : source.entrySet()){
            K k = entry.getKey();
            V v = entry.getValue();
            if(k instanceof Map<?,?>){
                k = (K) deepCopyMap((Map<?,?>) k);
            }
            if(v instanceof Map<?,?>){
                v = (V) deepCopyMap((Map<?,?>) v);
            }
            result.put(k, v);
        }
        return result;
    }
    

2
很棒的问题!结构清晰明了! - Michael
2个回答

9

泛型类型参数不能是自身的泛型。只需删除 M 的泛型定义:

public static <K, V, M extends Map<K, V>> M deepCopyMap(M s);

您所提到的泛型定义M<K,V>已经是隐含的,因为编译器必须确保M extends Map<K,V>是正确的。因此,M<K,V>的定义是多余的。

至于在方法内部创建副本,这更加复杂。泛型类型可以提高泛型方法用户的类型安全性。然而,在方法内部,您与使用非泛型方法采用原始Map作为参数一样没有头绪。(当然,您也可以进一步限制泛型类型。)

总之,我不建议您建议的做法。您建议API的用户您可以深度克隆任何类型的Map作为方法的参数。但是,您不能这样做。Map是一个公共接口,任何人都可以实现它。在运行时,您可能会被要求创建一个未知的深度克隆映射,而您将无法完成。看看这个实现:

@SupressWarnings("unchecked")
public static <K, V, M extends Map<K, V>> M deepCopyMap(M s) {
    Map map;
    if(s.getClass() == HashMap.class) {
      map = new HashMap();
    } else if(s.getClass == LinkedHashMap.class) {
      map = new LinkedHashMap();
    } else {
      throw new RuntimeException("unknown map type " + s.getClass());
    }
    for(Map.Entry<K, V> entry : source.entrySet()) {
        K k = entry.getKey();
        V v = entry.getValue();
        if(k instanceof Map) {
          map.put(k, deepCopyMap((Map) k));
        } else {
          result.put(k, v);
        }
    }
    return (M) map;
}

这对用户来说不太透明,如果地图包含某些用户类型的地图,很可能会抛出异常。编译器几乎会警告这个方法中的任何问题,这是一个坏主意的征兆。

相反,我实际上建议您采用重载方法的方法,仅为已知类型提供深度克隆。但是,如果您发现无法在运行时创建嵌套地图,则必须引发运行时异常。您正在寻找的类型安全性很难实现。此外,我将其作为合同的隐含部分,规定您不能在映射类型不属于指定的一组Map实现的情况下使用嵌套地图。

顺便说一句:如果没有限制MV,则定义这些参数是没有意义的,因为您不知道这些参数的任何信息。只需使用通配符?


我怎么没想到呢,感谢你清晰的回答!至于实现,我已经实现了两种不同的方法(都可以工作)。我会将它们添加到我的问题中,因为我似乎无法在评论中发布长代码片段。 - jmiserez
1
谢谢。只是要小心你在API中承诺了什么。泛型存在是为了提高编译时类型安全性。如果你只是隐藏了实际类型约束,那么你并没有改善使用原始类型的类型安全性。 - Rafael Winterhalter
我明白你的意思。我会采纳你的建议并列出所有类型。处理getClass().newInstance()方法的异常要麻烦得多,而简单地忽略或隐藏它们是不可行的,正如你所指出的那样。 - jmiserez

1
您定义的类型M已经绑定为Map<K,V>,并且使用<K, V, M extends Map<K, V>>。因此,只需删除M<K,V>,将其改为M即可。

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