【问题标题】:The type T is not generic; it cannot be parameterized with arguments <?> error in a generic function类型 T 不是通用的;它不能在泛型函数中使用参数 <?> 错误进行参数化
【发布时间】:2012-10-25 09:58:33
【问题描述】:

我想创建一个通用函数,它接受任何 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&lt;String, T&gt; 映射参数更改为 Map&lt;String, T&lt;?&gt;&gt; 来使其具有通用性,它会抱怨 the type T is not generic; it cannot be parameterized with arguments &lt;?&gt;

泛型参数本身可以泛型吗?

有没有办法创建满足上述要求的函数?

~更新

感谢大家的所有有见地的回答。

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

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

【问题讨论】:

  • 我认为你的问题是使用 Class.newInstance() 方法时没有已知的泛型参数。
  • 我认为我们不需要知道泛型类型,因为无论如何它们都会在运行时被删除。请指出,如果我错了。

标签: java generics collections


【解决方案1】:

在您的情况下,您需要最具体的类型,即 StringList.class 返回您Class&lt;List&lt;?&gt;&gt; 在您的情况下您需要特定类型,因此您需要添加演员Class&lt;List&lt;String&gt;&gt;

 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调用方法

【讨论】:

  • 我希望能够将 myMap 声明为 Map> 并将其作为参数传递,但它不允许这样做
  • 它给了我 java.lang.InstantiationException java.lang.InstantiationException: java.util.List at java.lang.Class.newInstance0(Class.java:340) at java.lang.Class.newInstance(Class.java:308) at com.test.Test.getMapValue(Test.java:35) at com.test.Test.main(Test.java:27)
  • @SumitJain 你不能实例化你需要实现的List类型
  • @SumitJain 更新了解决方案使用ArrayList.class
  • 你是如何设法学习 (Class>)(Class>) ArrayList.class 的!我认为令人讨厌是 C++ 的强项,正如现代 C++ 设计中所有血腥细节中所描述的那样,而不是 Java 的。但更大的问题是,使用这个魔法来创建一个通用函数(有些人可能不理解)或者只是懒惰并多次编写相同但更简单的代码会更好(请记住,这代码会被具有不同专业水平的多人阅读)?
【解决方案2】:

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

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

如果您总是使用列表映射,那么您可以声明采用Map&lt;String, List&lt;T&gt;&gt;的方法

【讨论】:

  • 感谢您的文章。不,我不能使用 List 因为我希望它对于任何类型的值都足够通用,而不仅仅是列表。
【解决方案3】:

这里的问题是您要求Class&lt;T&gt; 参数,而实际上无法(直接)获得Class&lt;List&lt;String&gt;&gt; 对象。

List.class 为您提供 Class&lt;List&gt;,但 List&lt;String&gt;.class 甚至无法编译(语法不正确)。

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

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

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

【讨论】:

  • 非常简洁的语法 this (Class>)(Class>)List.class。我在想这个魔法可以用一个通用函数来完成吗……嗯?可能是这样推得太远了。
  • @SumitJain 不幸的是没有 - 因为你最终会遇到同样的问题调用你的通用帮助函数,你现在有。您需要自己执行显式转换,因为您无法以完全类型安全的方式获取“通用类实例”(因为需要更准确的术语)。此外,Java 不支持更高种类的类型,因此无论如何您都不能完全通用地编写它。
【解决方案4】:

第一个示例适用于您使用不推荐的原始类型。

为了解决你的编译错误,你可能不希望将类限制为 T,而是允许一些超 T

public &lt;T&gt; T getValueFromMap(Map&lt;String, T&gt; map, String key, Class&lt;? super T&gt; 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&lt;String,List&lt;String&gt; myMap 呢?

我们应该知道ArrayListList 的超类型,但ArrayList&lt;String&gt; 不是List&lt;String&gt; 的超类型,但它是可赋值的。

那么让我们试试吧:

示例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],则结果。

【讨论】:

  • 是的,但是要使它起作用,我们必须将映射的值类型保持为具体类型。根据我的说法,我们不能在这里使用 Map> myMap编译器,但我还是不明白它的原因,为什么不能 Class super T> 当 T 为 List 时取 ArrayList.class,如果 T 为 ArrayList 时可以取,因为 List 是 ArrayList 的超类型)。
  • 原因是你不能创建 List 的实例,你可以初始化一个类型,如果 List 为 Set HashSet 创建 ArrayList 来映射一个 HashMap。但这可能会破坏需要 LinkedList 的预期代码部分。
  • @SumitJain,可能不是因为你,而是因为知道的追随者。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2016-06-01
  • 2020-08-13
  • 2017-07-05
  • 2015-06-15
  • 2013-04-30
  • 2019-06-18
  • 1970-01-01
相关资源
最近更新 更多