Java 泛型帮助:无法使用“Object”作为“?”的论点扩展对象”

发布于 2024-10-09 13:06:21 字数 1424 浏览 0 评论 0原文

我有以下代码:

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)

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

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

谢谢!

I have the following code:

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

The code fails to compile when I call valueMapPart1.put and valueMapPart2.put, with the error:

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

I have read on the Internet about generics and wildcards and captures, but I still don't understand what is going wrong. My understanding is that the value of the Map's can be any class that extends Object, which I think might be redundant, because all classes extend Object. And I cannot change the generics to something like ? super Object, because the Map is supplied by some library.

So why is this not compiling? Also, if I try to cast valueMap to Map<String,Object>, the compiler gives me that 'Unchecked conversion' warning.

Thanks!

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(3

此岸叶落 2024-10-16 13:06:21

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

如果您确实知道它们的返回类型是不可变的并且您单独拥有它,那么 @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”而不是扩展,如下所示:

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. 

If the library specifies extends then they are explicitly disallowing put. You should defensively copy before modifying, since they can quite legitimately change their return type to be immutable in a new version. If copying is expensive, then you can try creating a map type that is of type <String, Object> that first queries their map, and then queries some map you create that has your local modifications.

If you do know that their return type is immutable and that you solely own it, then the @SuppressWarnings("unchecked") annotations is a legitimate way to work around the warning, but I would double check those assumptions and comment extensively.

To understand extends vs super, look at it this way.
Since the value can be any type that extends Object, the following is valid.

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

So put doesn't really work with the extends boundary types.
But it works perfectly well with reading operations like get:

Object value = strToUnk.get("one");  // We don't know whether value is Integer or String, but it is an object (or null).

If you want a map to primarily use with "put" instead of "get", then you can use "super" instead of extends as in:

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. 
我要还你自由 2024-10-16 13:06:21

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

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

此处可以使用 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(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

As far as I know, bounded widecards, i.e. ? extends Number, is not used for variables or fileds. It is commonly used for arguments of method.

Let's first consider a case without generic type.

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

Example usages:

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

You can only pass a List of Number but not the List of Double to this method even if Double is subclass of Number.

The widecard generic can be used here to tell java compiler that this method can accept any list of subclass of Number.

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

Example usages:

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

However, you will no longer be able to modify the list object, e.g.

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

The above list now have type of "unknown subtype of Number" which can be List, List, List, etc. Adding a Double object to the list of unknown type is certainly unsafe. To illustrate this point, a call to method is

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
}

For variables and fields, you normally don't use bounded widecards, you can do

private Map<String, Object> origValueMap;

...

Map<String, Object> valueMapPart1 = origValueMap;
valueMapPart1.put(nameMappings[3], new Double(splitAtQuantity));

Note: there is no need to cast new Double(splitAtQuantity) to its super type, e.g Number or Object.

小红帽 2024-10-16 13:06:21

这确实涉及到一个古老的面向对象问题。乍一看,“一袋苹果”似乎是“一袋水果”的子类,但事实并非如此。对于面向对象的代码,您始终可以使用子类代替超类(这称为 里氏替换原则)。一袋苹果就打破了这一点,因为它接受橙子,而一袋水果接受橙子。

就问题而言, Collection couldCollection(它将接受您的 Double< /code>) 或者它可以是一个 Collection (这不会)。

This really goes to an old object-oriented gotcha. At first glance, it would seem that a "bag of apples" is a subclass of a "bag of fruit" but it is not. With object-oriented code, you can always use a subclass in place of a superclass (which is called the Liskov Substitution Principle). A bag of apples breaks this because it will not accept an orange whereas a bag of fruit would accept an orange.

In the terms of the question, Collection<?> could be a Collection<Object> (which would accept your Double) or it could be a Collection<Integer> (which would not).

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文