Java Generics:为什么我不能将一个方法中的Bounded Type Parameters实际传递给另一个方法?

时间:2012-12-30 14:00:37

标签: java generics collections bounded-types

我有一个代码(我正在测试Map接口)。

这是我的主要方法:

import blue.*;

public class Test {
    public static void main(String[] args) throws InterruptedException, java.io.IOException {

        MapCollections.<String, Number>frequencyTable(args);
    }
}

这是我使用的MapCollections课程:

package blue;
import java.util.*;
import static java.lang.System.out;

public class MapCollections {

   /**
   * The Generic Method. K is String and V is Number.
   */ 
   public static <K extends String, V extends Number> void frequencyTable(K[] args) {
        int initialCapacity = 10;

        // Now I pass the same types (K,V) to the getHashMap method
        // but it doesn't work, I get compile error on the hm.put() call, see a little below...
        // If I explicitly change K,V for String and Number it compiles fine, without errors
        Map<K, V> hm = MapCollections.<K,V>getHashMap(initialCapacity);

        // ... the error is
        // error: method put in interface Map<K#2,V#2> cannot be applied to given types
        hm.put("one", 1);
        hm.put("two", 2);
    };


    public static <K, V> Map<K, V> getHashMap(int initialCapacity) {

        return new HashMap<K,V>(initialCapacity);
    };
}

错误是:

C:\java>javac Test.java
.\blue\MapCollections.java:13: error: method put in interface Map<K#2,V#2> cannot be applied to given
 types;
                hm.put("one", 1);
                  ^
  required: K#1,V#1
  found: String,int
  reason: actual argument String cannot be converted to K#1 by method invocation conversion
  where K#1,V#1,K#2,V#2 are type-variables:
    K#1 extends String declared in method <K#1,V#1>frequencyTable(K#1[])
    V#1 extends Number declared in method <K#1,V#1>frequencyTable(K#1[])
    K#2 extends Object declared in interface Map
    V#2 extends Object declared in interface Map

我知道我无法将KV从一种方法转移到另一种方法,但它们的含义却失去了意义。

1)但为什么呢?

2)什么是K#1, V#1, K#2, V#2?为什么会出现其他数量?

3)有没有办法让KV按照我的意愿使用?

3 个答案:

答案 0 :(得分:2)

使用x extends AnyType时无法添加到集合中。

<强>原因: 考虑这个例子:

class Animal {

}
class Dog extends Animal {

}
class Cat extends Animal {

}

现在你有List<? extends Animal>

public void someMethod(List<? extends Animal> list) {
    list.add(new dog()); //no valid
}

然后你调用这样的方法:

List<Cat> catList = new ArrayList<Cat>(); 
somemethod(catList);

如果在使用带有扩展名的通配符时允许添加到集合中,则只需将Dog添加到仅接受Cat或子类型类型的集合中。因此,您无法在使用带有上边界的通配符的集合中添加任何内容。

BTW,字符串最终类k extends String虽然编译,但无效,因为没有什么可以扩展最终类的

答案 1 :(得分:1)

问题是K extends String可能是也可能不是String的子类(泛型忽略了类是final的事实)这意味着你可以更新键和值可能是这些班级的另一个子类。

Map<String, Double> map = new HashMap<>();
Map<String, ? extends Number> map2 = map; // ok
Number n = map2.get("Hello"); // ok
map2.put("Hello", 1); // not ok.

我会删除K extends String,因为它没有添加任何值,因为唯一有效的选项是String。如果您只打算查找,则可以V extends Number,而不是修改集合。

在这个具体的例子中,我只是将Number a Integer和它留在那里,除非你期望计数超过20亿。如果你担心空间我会使用TObjectIntHashMap,这使得更容易更新这样的集合。 e.g。

TObjectIntHashMap<String> counters = new TObjectIntHashMap<>();
counters.adjustOrPut("Hello", 1, 1); // one liner to add or increment for a key.

答案 2 :(得分:1)

按照给出的顺序回答您的问题(答案编号与您的问题相对应)

1)GanGnaMStYleOverFlowErroR已经回答了这个问题。让我试着用你自己的例子。

您已使用Test类中的类型参数调用frequencyTable方法

MapCollections.<String, Number>frequencyTable(args);

所以你希望你的hm.put(“one”,1)成功。 String是不可扩展的,但为了进一步解释,我们假设它是并假设有一个名为SplittableString的子类。

让我们说其他人正在使用您的代码并想要致电

MapCollections.<SplittableString, Number>frequencyTable(args);

他希望他的键成为子类型(SplittableString)。但是您正在使用父类'对象作为键加载(在您的方法中)。调用您的方法后,另一个开发人员尝试检索该密钥,假设他将获得SplittableString。但实际上他得到的是String,它不能分配给它的子类'reference。

2)你在这两种方法中所推出的K和V是不同的。

public static <K extends String, V extends Number> void frequencyTable(K[] args) {...}

public static <K, V> Map<K, V> getHashMap(int initialCapacity) {...}

为了在错误消息中区分它们,编译器使用K#1,K#2和V#1,V#2。它实际上试图将它们(在错误消息中)表示为

public static <K#1 extends String, V#1 extends Number> void frequencyTable(K[] args) {...}

public static <K#2, V#2> Map<K, V> getHashMap(int initialCapacity) {...}

3)不要将值放在你的hm.put(“one”,1)中,而是使用类似的东西

public static <K extends String, V extends Number> void frequencyTable(K[] args, V[] values) {
    pm.put(args[0], values[0]);
}

通过这种方式,你可以放置调用者想要输入的类型的对象,而不是放入不仅用户不想要的东西,而且还可能破坏类型的安全性。