exiter2000
exiter2000

Reputation: 548

Recursive call with Generic method in Java

I am working on legacy code like below

public Map myMethod(Map arg){
      Map newMap = createMap(); //This method creates a new Map instance.
      Iterator entries = arg.entrySet().iterator();
      while (entries.hasNext()) {
          Entry thisEntry = (Entry) entries.next();
          Object key = thisEntry.getKey();
          Object value = thisEntry.getValue();

          if ( value instanceof Map){
               Map newElement = myMethod((Map)value); // Recursive call here
               newMap.put(key, newElement);
          }else if ( value instanceof String ){
               newMap.put(key, value);
          }
      }
      return newMap;
}

Obviously, I would like to adapt Generics. So I changed the method like,

public <K,V> Map<K,V> myMethod(Map<K,V> arg){
      Map<K,V> newMap = createMap(); //creates a new Map instance.
      for ( Entry<K,V> entry : arg.entrySet()){

          K key = entry.getKey();
          V value = entry.getValue();

          if ( value instanceof Map){
               V newElement = myMethod(value); // Recursive call here
               newMap.put(key, newElement);
          }else if ( value instanceof String ){
               newMap.put(key, value);
          }
      }
      return newMap;
}

My question is about the line of recursive calls. So far, I have tried

  1. V newElement = myMethod(value); -> Compile Error
  2. Map<K,V> newElement = myMethod(value); ->compile Error
  3. V newElement = (V) myMethod((Map<?,?>)value); ->Type safety warning

--------- Edited ----------

The further assumption that I could make is

  1. createMap() method could be changed to createMap(arg)
  2. Element type of arg is not bounded to a specific Java type. This is a library method so it has to be generic

Upvotes: 1

Views: 643

Answers (2)

newacct
newacct

Reputation: 122429

The logic of your code is not type-safe, so it's not possible to avoid a type-safety warning.

You create a new map using createMap(); that method is creating a new map blindly, with no information, so the class of map it creates is not necessarily the same as the class of arg that is passed in. That means your method myMethod() returns a map that is not necessarily of the same implementing class as the one that is passed in.

In your recursive call, you pass a map that we know is an instance of V into the recursive call, and you want the thing you get back out to be a V. But that is not necessarily true. For example, what if V were TreeMap, and the thing that is returned from the recursive call is a HashMap? Then it should not be able to cast to V, and there is no way you can safely get a V from that.

Upvotes: 2

William Hipschman
William Hipschman

Reputation: 1

Your types are all messed up here.

Your method takes a

Map<K,V> 

and returns a

Map<K,V>

That means the call

V newElement = myMethod(element); 

must be

Map<K,V> newElement = myMethod(element);  //element is of type Map<K,V>

and the call

newMap.put(key, newElement);

means that newElement must also be of type V.

So you basically need String, Map, and V to all have some common super type. In the original code, Map is really

Map<Object, Object>

so the super type is Object.

I would back up, and ask yourself why you have a map that is storing both Maps and Strings. You should probably refactor them into objects that actually represent their types.

For instance, let's say it's a file system and the Map is a directory and the String is a File, then you should really create a FileObject that can be either a directory or a file, and your method signature becomes:

Map<K, FileObject> myMethod(Map<K, FileObject> map)

Upvotes: -1

Related Questions