可以将文章内容翻译成中文,广告屏蔽插件可能会导致该功能失效(如失效,请关闭广告屏蔽插件后再试):
问题:
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!
回答1:
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.
回答2:
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
.
回答3:
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).