Java Generics:通用映射(深层副本)的方法签名

jmi*_*rez 6 java generics map deep-copy

我有几个Map自己再次可能包含Maps(任何类型).我用签名写了一个方法:

public static <K,V> HashMap<K,V> deepCopyHashMap(HashMap<K,V> s);
Run Code Online (Sandbox Code Playgroud)

但是,我现在想概括一下这个代码来支持Maps,但仍然返回与参数类型相同的对象.所以代替:

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.
Run Code Online (Sandbox Code Playgroud)

我想要这样的东西:

public static <K,V, M extends Map<K,V>> M<K,V> deepCopyMap(M<K,V> s);
Run Code Online (Sandbox Code Playgroud)

但是,这给了我:

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>
Run Code Online (Sandbox Code Playgroud)

如何正确声明方法签名并仍然返回正确类型的对象(不在内部使用反射)?

对于这个项目,添加更多依赖项实际上不是一个选项,所以我更喜欢不依赖于外部库的解决方案.此外,我已经查看了Cloneable界面,但是它只是一个标记界面(Map一般没有s的实现),对我来说没有多大用处.


编辑:作为参考,这是我深层复制嵌套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;
}
Run Code Online (Sandbox Code Playgroud)

编辑:解决方案

  1. 这不是一个理想的解决方案.如果嵌套的运行时类型没有默认构造函数,它将失败Map.我已使用嵌套HashMaps 测试它,并正确复制了运行时类型.

    @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;
    }
    
    Run Code Online (Sandbox Code Playgroud)
  2. 这样更安全,但需要明确列出所有已知类型:

    @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;
    }
    
    Run Code Online (Sandbox Code Playgroud)

Raf*_*ter 9

通用类型参数不能是自己的通用.只需删除以下的通用定义M:

public static <K, V, M extends Map<K, V>> M deepCopyMap(M s);
Run Code Online (Sandbox Code Playgroud)

M<K, V>您声明的通用定义已经是隐式的,因为编译器必须确保这M extends Map<K, V>是真的.因此,定义M<K, V>是多余的.

至于在方法内部创建副本,它变得更复杂.通用类型改进了通用方法的用户的类型安全性.但是,在方法内部,你就像使用一个以raw Map作为参数的非泛型方法一样无能为力.(你当然可以进一步重写通用类型.)

毕竟,我不建议你采用你建议的方法.您建议API的用户可以深入克隆Map作为方法参数提供的任何类型的API .但是,你做不到.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;
}
Run Code Online (Sandbox Code Playgroud)

这对用户来说不是很普遍,如果地图包含一些用户类型地图,很可能会抛出异常.编译器会在这种方法中警告你几乎任何事情的事实是一个好迹象,这是一个坏主意.

相反,我实际上会建议你重载方法,你只为已知类型提供深度克隆.但是,如果您发现了无法在运行时创建的嵌套映射,则必须抛出运行时异常.您正在寻找的那种类型安全很难实现.另外,我会将它作为合同的隐含部分,您不能使用嵌套映射,其中映射类型不在指定的Map实现组中.

在一个侧面说明:不限制MV,有在确定这些参数,因为你不知道这些参数的任何事情没有任何意义.只需使用通配符即可?.