9

我想创建一个通用函数,该函数采用任何 Map 和一个 String 键,如果该键不存在于地图中,那么它应该创建一个值类型的新实例(已传递)并将其放入地图中然后把它返还。

这是我的实现

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

如果我将它与普通(非通用)列表一起用作值类型,它就可以工作,

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

4 回答 4

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

于 2012-10-25T10:00:56.713 回答
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

编辑

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

我们应该知道ArrayListis super type of List,但ArrayList<String>不是 super type ofList<String>但它是可赋值的。

所以让我们尝试一下:

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

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

对此的解决方案可能是假设如果类是 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]

于 2012-10-25T10:11:41.700 回答
1

由于泛型类型信息在运行时被删除,因此您不能使用本身为泛型的参数调用泛型方法。

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

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

于 2012-10-25T10:12:30.170 回答
1

这里的问题是您要求一个Class<T>参数,并且没有办法Class<List<String>>(直接)实际获取一个对象。

List.class给你一个Class<List>,但List<String>.class甚至不编译(它在语法上不正确)。

解决方法是自己将类对象转换为通用版本。您需要先向上转换,否则 Java 会抱怨类型不可分配:

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

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

于 2012-10-25T10:21:37.977 回答