Java Generics: method signature for (deep copy) shared maps

I have several Map , which themselves can again contain Map (of any type). I wrote a method with a signature:

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

However, now I would like to generalize this code to support Map in general, but still return an object of the same type as the argument. Therefore, instead of:

 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. 

I would like something like this:

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

However, this gives me:

 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> 

How to correctly declare a method signature and return an object of the correct type (without internal reflection)?

For this project, adding additional dependencies is really not an option, so I would prefer a solution that does not rely on external libraries. In addition, I looked at the Cloneable interface, but at the same time it was just a marker interface (without implementation for Map in general), this is not very useful for me.


Edit: For reference, this is my code for deep copying nested HashMap (code works correctly):

 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; } 

Edit: Solutions

  • This is not an ideal solution. It will fail if there is no default constructor for the environment type of the nested Map . I tested it with a nested HashMap , and the execution 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; } 
  • This is much safer, but all known types must be explicitly specified:

     @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; } 
+6
source share
2 answers

Type parameters cannot be on their own. Just drop the general definition for M :

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

The general definition of M<K, V> that you specified is already implicit, since the compiler must ensure that M extends Map<K, V> is true. Therefore, the definition of M<K, V> is redundant.

As for creating a copy inside the method, it becomes more complex. Generic types increase type safety for users of a generic method. However, inside the method you are as clueless as if you were using a non-generic method that took an unprocessed Map as an argument. (You could, of course, dwell on the general types.)

In the end, I would not recommend the approach you propose. You offer the user of your API that you can deeply clone any type of Map that is provided as an argument to a method. However you cannot. Map is an open interface, and anyone can implement it. At run time, you may be asked to create a deep cloning card that is unknown to you and you cannot. Take a look at this implementation:

 @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; } 

This is not very opaque to the user and will most likely throw an exception if the card contains some kind of user-type card. The fact that the compiler will warn you about anything in this method is a good sign that this is a bad idea.

Instead, I would recommend you an overload approach where you only offer deep clones for known types. If you, however, find a nested map that you cannot create at runtime, you need to throw a runtime exception. It is not possible to achieve the type of security you are looking for. In addition, I would make the implicit part of the contract that you cannot use nested maps where map types are not included in a specific group of Map implementations.

On the side of the note: without the limitations of M and V it makes no sense to define these parameters, since you know nothing about these parameters. Just use a wildcard ? .

+9
source

The type M you defined is already associated as Map<K,V> with <K, V, M extends Map<K, V>> . So just delete M<K,V> and just do it M

0
source

Source: https://habr.com/ru/post/959175/


All Articles