问题
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 to Map<String, T<?>>
it complains that the 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);
回答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
回答2:
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>>
回答3:
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.
回答4:
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]
.
来源:https://stackoverflow.com/questions/13066071/the-type-t-is-not-generic-it-cannot-be-parameterized-with-arguments-error-i