帮助Java Generics:不能使用“Object”作为“?extends Object”的参数

时间:2010-12-28 04:10:31

标签: java generics compiler-errors

我有以下代码:

import java.util.*;

public class SellTransaction extends Transaction {
    private Map<String,? extends Object> origValueMap;
    public SellTransaction(Map<String,? extends Object> valueMap) {
        super(Transaction.Type.Sell);
        assignValues(valueMap);
        this.origValueMap=valueMap;
    }
    public SellTransaction[] splitTransaction(double splitAtQuantity) {
        Map<String,? extends Object> valueMapPart1=origValueMap;
        valueMapPart1.put(nameMappings[3],(Object)new Double(splitAtQuantity));
        Map<String,? extends Object> valueMapPart2=origValueMap;
        valueMapPart2.put(nameMappings[3],((Double)origValueMap.get(nameMappings[3]))-splitAtQuantity);
        return new SellTransaction[] {new SellTransaction(valueMapPart1),new SellTransaction(valueMapPart2)};
    }
}

当我拨打valueMapPart1.putvalueMapPart2.put时,代码无法编译,错误:

The method put(String, capture#5-of ? extends Object) in the type Map is not applicable for the arguments (String, Object)

我已经在互联网上阅读过关于泛型,通配符和捕获的内容,但我仍然不明白出了什么问题。我的理解是Map的值可以是扩展Object的任何类,我认为这可能是多余的,因为所有类都扩展了Object。而且我无法将泛型更改为? super Object之类的内容,因为Map由某些库提供。

那为什么不编译呢?此外,如果我尝试将valueMap转换为Map<String,Object>,编译器会向我发出“未经检查的转化”警告。

谢谢!

3 个答案:

答案 0 :(得分:4)

如果图书馆指定extends,那么他们明确禁止put。你应该在修改之前进行防御性复制,因为他们可以非常合理地将他们的返回类型更改为在新版本中不可变。如果复制费用很高,那么您可以尝试创建类型为<String, Object>的地图类型,该类型首先查询其地图,然后查询您创建的具有本地修改的地图。

如果您确实知道他们的返回类型是不可变的并且您只拥有它,那么@SuppressWarnings("unchecked")注释是解决警告的合法方式,但我会仔细检查这些假设并进行广泛评论。 / p>

要了解extends vs super,请按照这种方式查看。 由于该值可以是扩展Object的任何类型,因此以下内容有效。

Map<String, Number> strToNum = new HashMap<String, Number>();
strToNum.put("one", Integer.valueOf(1));  // OK

Map<String, String> strToStr = new HashMap<String, String>();
strToStr.put("one", "1");  // OK

Map<String, ? extends Object> strToUnk = randomBoolean() ? strToNum : strToStr;
strToUnk.put("null", null);  // OK.  null is an instance of every reference type.
strToUnk.put("two", Integer.valueOf(2));  // NOT OK.  strToUnk might be a string to string map
strToUnk.put("two", "2");  // NOT OK.  strToUnk might be a string to number map

因此put并不真正适用于extends边界类型。 但它与get

等阅读操作完美配合
Object value = strToUnk.get("one");  // We don't know whether value is Integer or String, but it is an object (or null).

如果您希望地图主要使用“put”而不是“get”,那么您可以使用“super”而不是extend,如下所示:

Map<String, Number> strToNum = new HashMap<String, Number>();
Map<String, Object> strToObj = new HashMap<String, Object>();

Map<String, ? super Number> strToNumBase;
if (randomBoolean()) {
  strToNumBase = strToNum;
} else {
  strToNumBase = strToObj;
}

// OK.  We know that any subclass of Number can be used as values.
strToNumBase.put("two", Double.valueOf(2.0d));

// But now, gets don't work as well.
Number n = strToNumBase.get("one");  // NOT OK. 

答案 1 :(得分:3)

据我所知,bounded widecards,即? extends Number,不用于变量或文件。它通常用于方法的参数。

让我们首先考虑一个没有泛型的案例。

public void method(List<Number> list) {
}

示例用法:

method(new List<Double>()); // <-- Java compiler complains about this
method(new List<Number>()); // <-- Java compiler is happy with this.

即使ListNumber的子类,您也只能将List Double而非Double Number传递给此方法}。

这里可以使用widecard generic来告诉java编译器这个方法可以接受Number的任何子类列表。

public void method(List<? extends Number> list) {
}

示例用法:

method(new List<Double>()); // <-- Java compiler is happy with this.
method(new List<Number>()); // <-- Java compiler is happy with this.

但是,您将无法再修改列表对象,例如

public void method(List<? extends Number> list) {
    list.add(new Double()); // this is not allowed
}

上面的列表现在具有“{1}}的未知子类型”,可以是List,List,List等。将Number对象添加到未知类型的列表中肯定是不安全的。为了说明这一点,对Double的调用是

method

对于变量和字段,您通常不使用有界宽带,可以这样做

method(new ArrayList<Integer>());

...
public void method(List<? extends Number> list) {
    // adding Double to Integer list does not make sense.
    list.add(new Double()); // compiler error
}

注意:无需将private Map<String, Object> origValueMap; ... Map<String, Object> valueMapPart1 = origValueMap; valueMapPart1.put(nameMappings[3], new Double(splitAtQuantity)); 投射到其超级类型,例如new Double(splitAtQuantity)Number

答案 2 :(得分:0)

这真的是一个旧的面向对象的问题。乍一看,似乎“一袋苹果”是“水果袋”的子类,但事实并非如此。使用面向对象的代码,您始终可以使用子类代替超类(称为Liskov Substitution Principle)。一袋苹果打破了这个因为它接受橙色,而一袋水果接受橙色。

在问题的条款中,Collection<?> 可以Collection<Object>(可以接受您的Double),也可以是Collection<Integer> (不会)。