【问题标题】:Help with Java Generics: Cannot use "Object" as argument for "? extends Object"Java 泛型帮助:不能使用“对象”作为“?扩展对象”的参数
【发布时间】:2011-05-31 10:12:07
【问题描述】:

我有以下代码:

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时代码编译失败,报错:

类型Map中的put(String, capture#5-of ? extends Object)方法不适用于参数(String, Object)

我在 Internet 上阅读了有关泛型、通配符和捕获的信息,但我仍然不明白出了什么问题。我的理解是Map 的值可以是任何扩展 Object 的类,我认为这可能是多余的,因为所有类都扩展 Object。而且我不能将泛型更改为? super Object 之类的东西,因为Map 是由某个库提供的。

那么为什么不编译呢?另外,如果我尝试将 valueMap 转换为 Map&lt;String,Object&gt;,编译器会给出“未经检查的转换”警告。

谢谢!

【问题讨论】:

    标签: java generics compiler-errors


    【解决方案1】:

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

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

    要了解extendssuper,请这样看。 由于该值可以是扩展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”而不是 extends,如下所示:

    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 的作用,尽管我仍然不清楚为什么get 不能安全地与super 一起工作;我得再读一遍。
    • @AniDev, strToNumBase 可以是字符串到Number 的任何超类型的映射。 Number 的唯一超类型是 Number 本身、它的基类 Object 以及各种接口,如 ComparableCloneable 等。Integer 可以放入其中的任何一个,因为 @ 987654345@ 可以传递给put(Number n)put(Object o)put(Comparable&lt;?&gt; c)put(Cloneable c) 等中的任何一个。
    • 但是如果Map&lt;String, ? super Number&gt; strToNumBase = new HashMap&lt;String, Float&gt;();,我们尝试在Map 中添加Long 呢?
    • @AntiDev,这是非法的,因为Float 不是Number 的超类型。
    • Map&lt;String, ? super Number&gt; strToNumBase = new HashMap&lt;String, Double&gt;();怎么不违法?
    【解决方案2】:

    据我所知,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.
    

    即使DoubleNumber 的子类,您也只能将NumberList 传递给此方法,而不能将DoubleList 传递给此方法。

    这里可以使用widecard泛型告诉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

    【讨论】:

    • 谢谢,这有助于消除我对为什么我尝试做的事情不起作用的困惑。
    【解决方案3】:

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

    就问题而言,Collection&lt;?&gt;可以Collection&lt;Object&gt;(可以接受您的Double),也可以是Collection&lt;Integer&gt;(不可以)。

    【讨论】:

      猜你喜欢
      • 2023-02-02
      • 2012-05-08
      • 2023-04-07
      • 1970-01-01
      • 1970-01-01
      • 2013-03-20
      • 2023-01-09
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多