Reputation: 1265
JDK documentation of java.util.Stream interface has the following code snippet as an example of Collector construction.
Collector<Widget, ?, TreeSet<Widget>> intoSet =
Collector.of(TreeSet::new, TreeSet::add,
(left, right) -> { left.addAll(right); return left; });
The Collector.of method has the return type as static <T,R> Collector<T,R,R>
Does the ? in the return type in the example is just a convenient way to refer to the next generic type as those two have been declared as same in the method signature. And do all the following three statement are one and the same:
Collector<Widget, ?, TreeSet<Widget>> intoSet =
Collector.of(TreeSet::new, TreeSet::add,
(left, right) -> { left.addAll(right); return left; });
Collector<Widget, TreeSet<Widget>, TreeSet<Widget>> intoSet =
Collector.of(TreeSet::new, TreeSet::add,
(left, right) -> { left.addAll(right); return left; });
Collector<Widget, TreeSet<Widget>, ?> intoSet =
Collector.of(TreeSet::new, TreeSet::add,
(left, right) -> { left.addAll(right); return left; });
Upvotes: 3
Views: 467
Reputation: 3541
Does the ? in the return type in the example is just a convenient way to refer to the next generic type as those two have been declared as same in the method signature.
No, the ? has nothing to do with next generic type. In this specific example, we know ? happens to be R. This is called type inference, and it is inferred from the statement after the '='. You could surely define a Collector where ? refers to a different type than the last generic type.
And do all the following three statement are one and the same:
Yes, in terms of what intoSet really is. No, in terms of how intoSet can be further used
Don't let the grammar confuse you. This is just similar to
String str = "hello";
vs.
Object str = "hello";
In both cases, str is a string in runtime. This has nothing to do with how it is declared. But in the second case, you cannot use str as string, the compiler won't allow you because str is only declared as Object.
Same thing with generics. Java does not have declare-site variance but use-site variance. The wildcard "?" has nothing to do with how Collector.of is defined, but is all about how you want to use it.
Here Collector<Widget, ?, TreeSet<Widget>> intoSet = ...
tells you it is a collector that collects Widget into TreeSet. But how does it collect Widget into a TreeSet? More specifically, does it directly put each Widget into the set, or it uses an intermediate accumulator, e.g. a Stack<Widget>
, and eventually transforms the final result into TreeSet? Both of them are possible given Collector interface is defined as Collection<T,A,R>
. However, the intoSet declaration just says "I don't care, it is all up to how you initialize me".
You could even define it as Collector<?, ?, ?>
, totally fine.
As for the difference between
Collector<Widget, TreeSet<Widget>, TreeSet<Widget>> intoSet =
Collector.of(TreeSet::new, TreeSet::add,
(left, right) -> { left.addAll(right); return left; });
Collector<Widget, TreeSet<Widget>, ?> intoSet =
Collector.of(TreeSet::new, TreeSet::add,
(left, right) -> { left.addAll(right); return left; });
It once again goes to how you would like to use intoSet. For most of the time, we only care what is the input and output - for that input is the type we need to pass in, and output is the type we will eventually make use of. These two types tell a critical information about how the collector will interact with the rest of your code. Therefore you probably want to/have to specify them.
Collector<Widget, TreeSet<Widget>, ?> intoSet
is legal but probably less useful in practice. This is because when you want to use intoSet, you have no clue what is the type of result container (therefore you cannot use it meaningfully) by just looking at the declaration.
Similar example:
Map<?, ?> map = new HashMap<String, Integer>();
map.put("a", 1); //compiler complains here when you try to use map, as map is declared as <?,?>, not <String, Integer>
Upvotes: 1
Reputation: 45005
wildcard
?In generic code, the question mark (
?
), called thewildcard
, represents an unknown type. The wildcard can be used in a variety of situations: as the type of a parameter, field, or local variable; sometimes as a return type (though it is better programming practice to be more specific). The wildcard is never used as a type argument for a generic method invocation, a generic class instance creation, or a supertype.
Collector.of
?As you have already noticed Collector.of(Supplier<R> supplier, BiConsumer<R, T> accumulator, BinaryOperator<R> combiner, Characteristics... characteristics)
returns an object of type Collector<T, R, R>
knowing that the class Collector
has 3 type parameters which are T
, A
and R
where:
<T>
: The type of input elements to the reduction operation.<A>
: The mutable accumulation type of the reduction operation (often hidden as an implementation detail)<R>
: The result type of the reduction operationSo as described in the javadoc we generally use a wildcard for the type parameter <A>
because it is considered as an implementation detail since it is only an intermediate type, what really matter are the input and output type parameters respectively T
and R
so for the sake of simplicity/readability ?
is preferred to what should theoretically be used which is TreeSet<Widget>
in this case.
Upvotes: 6
Reputation: 5034
In Java generics, the '?' is a pure wildcard that will match any type, regardless of any other types in the signature (and there is no syntax for "the next generic type").
Remember that, due to erasure, the types specified in the signature are for compile-time checking only, not for run-time. That means that those three statements will all execute exactly the same. The only difference between them will be if you want generic (compile-time) type checking for the different types.
Finally, note that there is also a :
static <T,A,R> Collector<T,A,R> of
method, so there could be all different types.
Upvotes: 1