The type T is not generic; it cannot be parameterized with arguments <?> error in a generic function
Solution 1
You need most specific types which is String
in your case. List.class
returns you Class<List<?>>
in your case you need specific type so you will need to add cast Class<List<String>>
Map<String,List<String>> myMap = new HashMap<String, List<String>>();;
List<String> value = getValueFromMap(myMap, "aKey",
(Class<List<String>>)(Class<?>)ArrayList.class) ;
Now if you invoke method like below
getValueFromMap(myMap, "aKey", List.class)
^^^T is List<String> ^^^ T is List<?> wild char since List.class
will return you Class<List<?>>
So your defination of T
is causing confusion to Compiler so you are getting compile error.
You can not create instance of List.class
since it is interface you will need implemention class of it so call method with ArrayList.class
Solution 2
The issue here is that you're asking for a Class<T>
parameter, and there is no way to actually get a Class<List<String>>
object (directly).
List.class
gives you a Class<List>
, but List<String>.class
doesn't even compile (it's not syntactically correct).
The workaround is to cast the class object yourself into the generic version. You'll need to upcast first as otherwise Java will complain that the types aren't assignable:
Class<List<String>> clz = (Class<List<String>>)(Class<?>)List.class;
Since generics are implemented via erasure, the casting doesn't fundamentally do anything - but it keeps the compiler happy with its inference of what T
is, and allows flexible generic methods like yours to work. This is not an uncommon workaround to need to use when you want to pass in a Class<T>
token.
Solution 3
The first example workds becase you are using raw type that is not recomended.
To solve your compilation error you might want to not limit the class into T but allow somethi g super the T
public <T> T getValueFromMap(Map<String, T> map, String key, Class<? super T> valueClass)
But then you will have not save convertion and you will need to cast result of new instance into (T). And you can not use List as the class because is an iterface and you can not create a instace of it.
public <K,V> V getValueFromMap(Map<K, V> map, K key, Class<? super V> valueClass){
if(map.containsKey(key) == false) {
try {
map.put(key, (V) valueClass.newInstance());
} catch (InstantiationException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
}
}
return map.get(key);
}
Map<String,ArrayList<String>> myMap = new HashMap<String, ArrayList<String>>();
List<String> value = getValueFromMap(myMap, "aKey", ArrayList.class); //will work
EDIT
But what about the base form Map<String,List<String> myMap
, that OP expect ?
As we should know ArrayList
is super type of List
, but ArrayList<String>
is not super type of List<String>
but it is assignable.
So lets try it out:
Example 1: getValueFromMap(myMap, "aKey", ArrayList.class); //Compilation error
Example 2: getValueFromMap(myMap, "aKey", List.class); //Runtime error
Solution for this might be some assumption that if class is List then create ArrayList.
This force us to create some not decent code.
public static <V> V createInstace(Class<V> valueClass) throws InstantiationException, IllegalAccessException {
if(List.class.isAssignableFrom(valueClass)) {
return (V) new ArrayList<V>();
}
return valueClass.newInstance();
}
This code do not really have anything common with generics but is working.
At the end we finish with
public static void main(String[] args) {
Map<String,List<String>> myMap = new HashMap<String, List<String>>();
List<String> value = getValueFromMap(myMap, "aKey", List.class); //does not work
value.add("Success");
System.out.println(value);
}
public static <K,V> V getValueFromMap(Map<K, V> map, K key, Class<? super V> valueClass){
if(map.containsKey(key) == false) {
try {
map.put(key, (V) createInstace(valueClass));
} catch (InstantiationException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
}
}
return map.get(key);
}
public static <V> V createInstace(Class<V> valueClass) throws InstantiationException, IllegalAccessException {
if(List.class.isAssignableFrom(valueClass)) {
return (V) new ArrayList<V>();
}
return valueClass.newInstance();
}
And the result if this is [Sucess]
.
Solution 4
You can not call generic methods with parameters that are themshelves generic as the generic type information is erased at runtime.
See http://www.angelikalanger.com/GenericsFAQ/FAQSections/ProgrammingIdioms.html#FAQ004
If you always use map of lists then may be you can declare the method to take Map<String, List<T>>
Related videos on Youtube
Sumit Jain
Updated on October 27, 2020Comments
-
Sumit Jain over 3 years
I want to create a generic function that takes any Map & a String key, if the key is not present in the map, then it should create a new instance of the Value Type (which is passed) & put it in the map & then return it.
Here is my implementation
public <T> T getValueFromMap(Map<String, T> map, String key, Class<T> valueClass){ T value = map.get(key); if (value == null){ try { value = valueClass.newInstance(); } catch (InstantiationException e) { // TODO Auto-generated catch block e.printStackTrace(); } catch (IllegalAccessException e) { // TODO Auto-generated catch block e.printStackTrace(); } map.put(key, value); } return value; }
It works if I use it with a normal (not generic) List as the value type,
Map<String,List> myMap; List value = getValueFromMap(myMap, "aKey", List.class) //works
but not with generic type lists
Map<String,List<String>> myMap; List<String> value = getValueFromMap(myMap, "aKey", List.class) //does not work
Also if I try to make
Map<String, T>
map parameter generic by changing it toMap<String, T<?>>
it complains thatthe type T is not generic; it cannot be parameterized with arguments <?>
Can the generic parameters be themselves made generic?
Is there any way to create function with above mentioned requirements?
~Update
Thanks for all the insightful answers everyone.
I have verified that the accepted solution works for any value type with this code
Map<String, Map<String, List<String>>> outer = new HashMap<String, Map<String,List<String>>>(); Map<String, List<String>> inner = getValueFromMap(outer, "b", (Class<Map<String, List<String>>>)(Class<?>)HashMap.class); List<String> list = getValueFromMap(inner, "a", (Class<List<String>>)(Class<?>)ArrayList.class);
-
Sumit Jain over 11 yearsYes, but for this to work we have to keep the value type of the map to be the concrete type..we can not use Map<String,List<String>> myMap here according to my compiler, but I still don't understand the reason for it, why can't Class<? super T> take ArrayList.class when T is List<String> if it can take it when T is ArrayList<String>, as List is supertype of ArrayList).
-
Sumit Jain over 11 yearsvery neat piece of syntax this (Class<List<String>>)(Class<?>)List.class. I am thinking can this piece of magic be done using a generic function..huh? may be that would be pushing it too far.
-
Andrzej Doyle over 11 years@SumitJain unfortunately not - because you'd end up with the same problem calling your generic helper function, that you have at the moment. You need to perform an explicit cast yourself because you can't get the "generic class instance" (for want of a more exact term) in a completely type-safe way. Also Java doesn't have support for higher-kinded types, so you couldn't write this completely generically anyway.
-
Damian Leszczyński - Vash over 11 yearsThe reason of that is you can not create instace of List, you could have a type initialized that in case of List creates ArrayList for Set HashSet for Map a HashMap. But this might corrupte expected part of code where a LinkedList is required.
-
Damian Leszczyński - Vash over 11 years@SumitJain, For you may it not be, but for followers who knows.