T型不是通用的;它不能用泛型函数中的参数<! - ? - >参数化参数化

时间:2012-10-25 09:58:34

标签: java generics collections

我想创建一个通用函数,可以使用任何Map&amp;一个字符串键,如果映射中没有键,那么它应该创建一个值类型的新实例(传递)&amp;把它放在地图上然后归还。

这是我的实施

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

如果我将它与普通(非通用)List一起用作值类型

,它就可以工作
Map<String,List> myMap;
List value = getValueFromMap(myMap, "aKey", List.class) //works

但不是通用类型列表

Map<String,List<String>> myMap;
List<String> value = getValueFromMap(myMap, "aKey", List.class) //does not work

此外,如果我尝试将Map<String, T>地图参数更改为Map<String, T<?>>,则会抱怨the type T is not generic; it cannot be parameterized with arguments <?>

通用参数本身可以是通用的吗?

有没有办法创建具有上述要求的功能?

〜更新

感谢大家的所有见解。

我已验证所接受的解决方案适用于使用此代码的任何值类型

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

4 个答案:

答案 0 :(得分:5)

您需要大多数特定类型,String。在您需要特定类型的情况下,List.class会向您返回Class<List<?>>,因此您需要添加广告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) ;

现在,如果你调用下面的方法

 getValueFromMap(myMap, "aKey",      List.class) 
                ^^^T is List<String>  ^^^ T is List<?> wild char since List.class
                                          will return you Class<List<?>>

因此,您对T的定义会导致编译器混淆,从而导致编译错误。

您无法创建List.class的实例,因为它是您需要实现类的接口,因此使用ArrayList.class调用方法

答案 1 :(得分:1)

第一个示例是因为您使用的是未建议的原始类型。

要解决编译错误,您可能不希望将类限制为T但允许某些超级T

public <T> T getValueFromMap(Map<String, T> map, String key, Class<? super T> valueClass)

但是你将无法保存转换,你需要将新实例的结果转换为(T)。并且你不能使用List作为类,因为它是一个iterface,你不能创建它的实例。

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

修改

但OP期望的基本形式Map<String,List<String> myMap呢?

我们应该知道ArrayListList的超级类型,但ArrayList<String>不是List<String>的超级类型,但它是可分配的。

让我们试一试:

示例1:getValueFromMap(myMap, "aKey", ArrayList.class); //Compilation error

示例2:getValueFromMap(myMap, "aKey", List.class); //Runtime error

对此的解决方案可能是假设如果class是List则创建ArrayList。

这迫使我们创建一些不合适的代码

public static <V> V createInstace(Class<V> valueClass) throws InstantiationException, IllegalAccessException {

        if(List.class.isAssignableFrom(valueClass)) {
            return (V) new ArrayList<V>();
        }

        return valueClass.newInstance();

    }

这段代码与泛型有任何共同之处但是有效。

最后我们以

结束
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();

    }

结果如果是[Sucess]

答案 2 :(得分:1)

由于泛型类型信息在运行时被删除,因此无法使用具有通用类型信息的参数调用泛型方法。

请参阅http://www.angelikalanger.com/GenericsFAQ/FAQSections/ProgrammingIdioms.html#FAQ004

如果您总是使用列表地图,那么您可以声明采用Map<String, List<T>>的方法

答案 3 :(得分:1)

这里的问题是你要求Class<T>参数,并且无法直接获得Class<List<String>>个对象。

List.class为您提供Class<List>,但List<String>.class甚至无法编译(在语法上不正确)。

解决方法是将类对象自己转换为通用版本。你需要先进行upcast,否则Java会抱怨这些类型不可分配:

Class<List<String>> clz = (Class<List<String>>)(Class<?>)List.class;

由于泛型是通过擦除实现的,因此转换不会从根本上做任何事情 - 但它使编译器对T的推断感到满意,并允许像你这样灵活的通用方法工作。当您想要传递Class<T>令牌时,这不是一种不常见的解决方法。