【问题标题】:Java Generics: method signature for (deep copy of) generic MapsJava 泛型:泛型 Maps(深拷贝)的方法签名
【发布时间】:2013-12-18 06:44:36
【问题描述】:

我有几个Maps,它们本身可能又包含Maps(任何类型)。我写了一个带有签名的方法:

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

但是,我现在想概括此代码以支持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.

我想要这样的东西:

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 接口,但是它只是一个标记接口(一般没有实现Maps),它对我没有多大用处。


编辑: 作为参考,这是我用于深度复制嵌套HashMaps 的代码(代码可以正常工作):

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. 这不是一个理想的解决方案。如果嵌套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;
    }
    
  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;
    }
    

【问题讨论】:

  • 好问题!条理清晰,条理清晰!

标签: java generics map deep-copy


【解决方案1】:

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

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

您所说的通用定义M&lt;K, V&gt; 已经是隐含的,因为编译器必须确保M extends Map&lt;K, V&gt; 为真。因此,定义M&lt;K, V&gt; 是多余的。

至于在方法内部创建副本,它变得更加复杂。泛型类型提高了泛型方法用户的类型安全性。但是,在该方法内部,您就像使用以原始 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,定义这些参数是没有意义的,因为您对这些参数一无所知。只需使用通配符?

【讨论】:

  • 我怎么没看到,感谢您的明确回答!至于实施,我实施了两种不同的方法(两种都有效)。我会将它们添加到我的问题中,因为我似乎无法在 cmets 中发布长的 sn-ps 代码。
  • 谢谢。请注意您对 API 的承诺。存在泛型以提高编译时类型安全。如果你只是隐藏了实际的类型约束,你并没有提高使用原始类型的类型安全性。
  • 我明白你的意思。我同意你的建议,并明确列出所有类型。必须处理 getClass().newInstance() 方法的异常要麻烦得多,并且正如您正确指出的那样,简单地忽略它们或隐藏它们不是一种选择。
【解决方案2】:

您定义的类型M 已经绑定为Map&lt;K,V&gt;&lt;K, V, M extends Map&lt;K, V&gt;&gt;。所以只需删除M&lt;K,V&gt; 并使其成为M

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2019-01-06
    • 1970-01-01
    • 1970-01-01
    • 2021-12-27
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多