Home > other >  Generics: How to populate a map from arrays?
Generics: How to populate a map from arrays?

Time:01-08

I tried to write a utility method which would populate a map from a key and a value array, no matter what the data type of key and value would be.

  public static <K,V> Map<K,V> fillMap(Map<K,V> map, K[] keys, V[] values) {
    int l= keys.length;
    for (int i=0; i<l; i  )
      map.put(keys[i], values[i]);
    return map;
  }

Then I called the method with

    HashMap<Integer, String> map= new HashMap<>();
    Integer[] keys= IntStream.range(0, 12).boxed().toArray(Integer[]::new);
    String[] values= new String[] {"Jan","Feb","Mar","Apr","Mai","Jun",
                   "Jul","Aug","Sep","Okt","Nov","Dez"};
    map= MyUtil.fillMap(map, keys, values);

and received the error:
incompatible types: no instance(s) of type variable(s) K,V exist so that Map<K,V> conforms to HashMap<Integer,String>
All attempts to replace <K,V> by variations of <? extends Object> and the like failed so far. How can this be fixed?

CodePudding user response:

Problem

In the code presented, we declare:

HashMap<Integer, String> map = new HashMap<>();

and

public static <K,V> Map<K,V> fillMap(Map<K,V> map, K[] keys, V[] values)

Hence, if we call

map = MyUtil.fillMap(map, keys, values);

we try to assing a Map<...> (returned by MyUtil::fillMap) to a HashMap<...>. This cannot work since a Map is not a HashMap.


Possible solutions

There are two ways that come to my mind to fix this issue:

  1. either change the type of map,
  2. or make the return-type of MyUtil::fillMap generic.

1. change the type of map:

We can change the type of map from HashMap<...> to Map<...>:

Map<Integer, String> map = new HashMap<>();
...
map = MyUtil.fillMap(map, keys, values);

Ideone demo

2. Make the return-type of MyUtil::fillMap generic:

By adding an additional generic parameter, we can make the concrete implementation of the return type generic as well:

public static void main(String[] args) {
  HashMap<Integer, String> map = new HashMap<>();
  final Integer[] keys = IntStream.range(0, 12).boxed().toArray(Integer[]::new);
  final String[] values = new String[] {"Jan", "Feb", "Mar", "Apr", "Mai", "Jun",
      "Jul", "Aug", "Sep", "Okt", "Nov", "Dez"};
  map = MyUtil.fillMap(map, keys, values);
  System.out.println(map);
}

public static <K, V, M extends Map<K, V>> M fillMap(M map, K[] keys, V[] values) {
  final int l = keys.length;
  for (int i = 0; i < l; i  ) {
    map.put(keys[i], values[i]);
  }
  return map;
}

Ideone demo


Bonus: state-free construction of the return-value

If it is not necessary to pass-in the concrete implementation of the map used to the method, I would propose a third option that creates the map to return within the method:

public static <K, V> Map<K, V> fillMap(K[] keys, V[] values) {
  return IntStream.range(0, keys.length)
      .boxed()
      .collect(Collectors.toMap(
          index -> keys[index],
          index -> values[index]));
}

Ideone demo

  •  Tags:  
  • Related