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

29,469

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>>

Share:
29,469

Related videos on Youtube

Sumit Jain
Author by

Sumit Jain

Updated on October 27, 2020

Comments

  • Sumit Jain
    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 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);
    
  • Sumit Jain
    Sumit Jain over 11 years
    Yes, 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
    Sumit Jain over 11 years
    very 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
    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
    Damian Leszczyński - Vash over 11 years
    The 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
    Damian Leszczyński - Vash over 11 years
    @SumitJain, For you may it not be, but for followers who knows.