Going straight to the point (I know that one should avoid using wildcard types as the returning type)
I was writing this answer and the following code:
public static Map<?, Long> manualMap(Collection<?> c){
Map<?, Long> map = new HashMap<>();
c.forEach(e -> map.compute(e, (k, v) -> (v == null) ? 1 : v + 1));
return map;
}
which gets the following warning:
Required type: capture of ?
Provided: capture of ?
and a suggestion from IntelliJ to
Change variable 'map' to 'Map<?, Object'
which makes even less sense. Naturally, it fails when I try to apply the suggestion.
Originally, I though "okey it has to do with the fact that it does not match the compute signature", namely:
default V compute(K key, ...)
So I tried
public class MyMap <T>{
public static <T> void nothing(Collection<T> c){
// Empty
}
}
and
public static Map<?, Long> manualMap(Collection<Collection<?>> c, Map<?, Long> map){
c.forEach(MyMap::nothing);
return map;
}
and I had no issue.
The following two versions:
public static <T> Map<?, Long> manualMap(Collection<?> c){
Map<T, Long> map = new HashMap<>();
c.forEach(e -> map.compute((T) e, (k, v) -> (v == null) ? 1 : v + 1));
return map;
}
and
public static Map<?, Long> manualMap(Collection<?> c){
Map<Object, Long> map = new HashMap<>();
c.forEach(e -> map.compute(e, (k, v) -> (v == null) ? 1 : v + 1));
return map;
}
work without any problem (except for a warning in the (T) the case).
So the question is
Why the first version does not work?
The use of wildcard types should be limited to method parameters. This rule raises an issue when a method returns a wildcard type. Noncompliant Code Example List<? extends Animal> getAnimals () {...}
The wildcard can be used in a variety of situations such as the type of a parameter, field, or local variable; sometimes as a return type. Unlike arrays, different instantiations of a generic type are not compatible with each other, not even explicitly. This incompatibility may be softened by the wildcard if ? is used as an actual type parameter.
You can use the "in" and "out" principle when deciding whether to use a wildcard and what type of wildcard is appropriate. The following list provides the guidelines to follow: An "in" variable is defined with an upper bounded wildcard, using the extends keyword.
Wildcard Guidelines: An "in" variable is defined with an upper bounded wildcard, using the extends keyword. An "out" variable is defined with a lower bounded wildcard, using the super keyword. In the case where the "in" variable can be accessed using methods defined in the Object class, use an unbounded wildcard.
The first method does not compile because of "capture conversion", that happens at each declaration. You can read my other answer about this, or this one. But to put it simply, you will have two separate types there, which you can see when you compile via:
javac --debug=verboseResolution=all
And the output will contain:
.....
CAP#1 extends Object from capture of ?
CAP#2 extends Object from capture of ?
...
which means there are two types that have been capture converted. These types are unrelated to each other, the way you have it.
This on the other hand:
public static <T> void nothing(Collection<T> c){
}
is called wildcard capture method (it "captures" a wildcard) and is documented in official tutorial on why it works and how; thus you have no problem with that.
But the main problem here is that you can't assign anything (other than null
) to a wildcard. So in your compute
example, the first argument is going to be inferred to a ?
and you can't assign anything to that.
public static Map<?, Long> manualMap(Collection<?> c){
Map<?, Long> map = new HashMap<>();
c.forEach(e -> map.compute(e, (k, v) -> (v == null) ? 1 : v + 1));
return map;
}
Here, you have three wildcards: the one on the parameter, the one on the map variable, and the one on the return value. (The return value one isn't super relevant).
You are trying to pass elements from the collection (of one wildcard type) into a method of the map (of another wildcard type).
The compiler doesn't know that those two are "meant to be" the same, so it doesn't accept a "collection" wildcard where it needs a "map" wildcard.
You can indicate that they are the same type via a type variable:
public static <T> Map<?, Long> manualMap(Collection<T> c){
Map<T, Long> map = new HashMap<>();
c.forEach(e -> map.compute(e, (k, v) -> (v == null) ? 1 : v + 1));
return map;
}
Here, T is a type that you don't know; but you know it's the same unknown type in both cases.
Or you can just declare it in a way that doesn't need two wildcards:
return c.stream().collect(groupingBy(a -> a, counting()));
The problem is that your method is accepting a collection of type "I don't care", but you're also creating a map with keys of "I don't care". That does not mean the types you do not care about are the same types. For that you need to actually bind the type, by using a type variable e.g.
public static <T> Map<T, Long> manualMap(Collection<T> c){
Map<T, Long> map = new HashMap<>();
c.forEach(e -> map.compute(e, (k, v) -> (v == null) ? 1 : v + 1));
return map;
}
The method will still accept any types of collections, but it cares enough to keep track of the type and there's no wildcards or warnings anywhere, yay!
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With