Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Java Generics: method signature for (deep copy of) generic Maps

I have a few Maps that themselves again may contain Maps (of any type). I wrote a method with the signature:

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

However, I would now like to generalize this code to support Maps in general, but still return an object of the same type as the argument. So 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 do I properly declare the method signature and still return an object of the correct type (without using reflection internally)?

For this project adding more dependencies is really not an option, so I would prefer a solution that does not rely on external libraries. Also, I have looked into the Cloneable interface, however with it being only a marker interface (with no implementation for Maps in general) it is not of much use to me.


Edit: For reference, this is my code for deep-copying nested HashMaps (code works properly):

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

  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;
    }
    
like image 691
jmiserez Avatar asked Dec 02 '13 14:12

jmiserez


People also ask

How to create a generic map in Java?

In a generic Map you need to define the type of the key and the type of the value of object stored in the Map.you can set the specific type of both the keys and values in a generic Map instance. The syntax for creating a generic Map is given below.

How to create a generic map in Salesforce?

In a generic Map you need to define the type of the key and the type of the value of object stored in the Map.you can set the specific type of both the keys and values in a generic Map instance. The syntax for creating a generic Map is given below. Here K is the type of map key and V is the type of the value stored in the map.

What are the properties of generic methods in Java?

These are some properties of generic methods: Generic methods have a type parameter (the diamond operator enclosing the type) before the return type of the method declaration. Type parameters can be bounded (we explain bounds later in this article). Generic methods can have different type parameters separated by commas in the method signature.

What is the best way to represent a generic type in Java?

Note that Oracle recommendation is to use an uppercase letter to represent a generic type and to choose a more descriptive letter to represent formal types. In Java Collections, we use T for type, K for key and V for value. 3.1. Bounded Generics Remember that type parameters can be bounded.


1 Answers

Generic type parameters cannot be themself generic. Just drop the generic definition for M:

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

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

As for creating a copy inside of the method it gets more complicated. Generic types improve type safety for the users of a generic method. However, inside the method you are just as clueless as if you used a non-generic method which took a raw Map as its argument. (You could of course further restict the generic types.)

After all, I would not recommend you the approach you suggest. You are suggesting the user of your API that you could deeply clone any type of Map that is provided as the method's argument. However, you cannot. Map is a public interface and anybody can implement it. At run time you might be ask to create a deep clone map that is unknown to you and you will not be able to. 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 transperent to the user and will most likely throw an exception if the map contains some user type map. The fact that the compiler will warn you about almost anything in this method is a good sign for that this is a bad idea.

Instead, I would actually recommend you the overloading approach where you only offer deep clones for known types. If you however discover a nested map that you cannot create at run time, you have to throw a run time exception. The kind of type safety you are looking for is hard to achieve. Additionally, I would make it an implicit part of the contract that you cannot use nested maps where the map types are not within a specified group of Map implementations.

On a side note: Without restricting M and V, there is no point in defining these parameters since you do not know anything about these parameters. Simply use a wildcard ?.

like image 109
Rafael Winterhalter Avatar answered Sep 29 '22 02:09

Rafael Winterhalter