繁体   English   中英

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

[英]Help with Java Generics: Cannot use “Object” as argument for “? extends Object”

我有以下代码:

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 ? super Object ,因为Map是由某些库提供的。

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

谢谢!

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

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

要了解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. 

据我所知, 有界宽的卡片 ,即? extends Number ? 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.

即使DoubleNumber子类,也只能将List of Number而不是List of Double传递给此方法。

这里可以使用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
}

上面的列表现在具有“未知的Number子类型”类型,可以是List,List,List等。将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)转换为其超类型,例如NumberObject

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

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

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM