The type T is not generic; it cannot be parameterized with arguments <?> error in a generic function

 ̄綄美尐妖づ 提交于 2021-02-18 06:05:33

问题


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

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!