java.util.Stream接口的JDK文档有以下代码片段作为收集器构造的示例。
Collector<Widget, ?, TreeSet<Widget>> intoSet =
Collector.of(TreeSet::new, TreeSet::add,
(left, right) -> { left.addAll(right); return left; });Collector.of方法的返回类型为static <T,R> Collector<T,R,R>
是吗?在示例中的返回类型中,引用下一个泛型类型只是一种方便的方法,因为这两个类型在方法签名中声明为相同。下面的三条语句都是一样的:
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; });发布于 2016-10-22 15:04:29
什么是wildcard?
在泛型代码中,问号(
?) (称为wildcard)表示--一个未知类型的。通配符可以在多种情况下使用:作为参数、字段或局部变量的类型;有时作为返回类型(尽管更具体的编程实践更好)。通配符从未用作泛型方法调用、泛型类实例创建或超类型的类型参数。
为什么它与Collector.of一起使用?
正如您已经注意到的,Collector.of(Supplier<R> supplier, BiConsumer<R, T> accumulator, BinaryOperator<R> combiner, Characteristics... characteristics)返回一个类型为Collector<T, R, R>的对象,知道类Collector有3个类型参数,即T、A和R,其中:
<T>:还原操作的输入元素的类型。<A>:还原操作的可变积累类型(通常隐藏为实现细节)<R>:还原操作的结果类型因此,正如javadoc中所描述的,我们通常使用通配符作为类型参数<A>,因为它被认为是一个实现细节,因为它只是一个中间类型,真正重要的是输入和输出类型参数分别是T和R,因此为了简单/可读性起见,在这种情况下首选?而不是理论上应该使用的TreeSet<Widget>。
发布于 2016-10-17 03:00:03
在Java泛型中,“?”是一个纯通配符,它将匹配任何类型,而不管签名中的任何其他类型(并且“下一个泛型类型”没有语法)。
请记住,由于擦除,签名中指定的类型仅用于编译时检查,而不是用于运行时。这意味着这三个语句都将执行完全相同的操作。它们之间的唯一区别将是如果您希望对不同类型进行泛型(编译时)类型检查。
最后,请注意,还有:
static <T,A,R> Collector<T,A,R> of方法,因此可能存在各种不同的类型。
发布于 2016-10-26 18:18:24
是吗?在示例中的返回类型中,引用下一个泛型类型只是一种方便的方法,因为这两个类型在方法签名中声明为相同。
不,那个?与下一个泛型类型无关。在这个具体的例子中,我们知道吗?碰巧是R,这被称为类型推断,它是从'=‘之后的语句中推断出来的。你肯定可以定义一个收藏家在哪里?引用与上一个泛型类型不同的类型。
下面的三条语句都是一样的:
是的,就intoSet到底是什么而言。不,就如何进一步使用intoSet而言
别让语法弄糊涂了。这就像
String str = "hello";与
Object str = "hello";在这两种情况下,str都是运行时中的字符串。这与声明的方式无关。但是在第二种情况下,不能使用str作为字符串,编译器将不允许您,因为str只声明为Object。
仿制药也是如此。Java没有声明-站点差异,而是使用-站点差异。通配符"?“与如何定义Collector.of无关,而与您希望如何使用它有关。
这里,Collector<Widget, ?, TreeSet<Widget>> intoSet = ...告诉您,它是一个将Widget收集到TreeSet中的收集器。但是它如何将Widget收集到TreeSet中呢?更具体地说,它是直接将每个Widget放入集合,还是使用中间累加器(例如Stack<Widget> ),并最终将最终结果转换为TreeSet?这两者都是可能的,因为收集器接口被定义为Collection<T,A,R>。然而,intoSet声明只是说“--我不在乎,这完全取决于您如何初始化我”。
您甚至可以将其定义为Collector<?, ?, ?>,完全可以。
至于…之间的区别
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; });这再次说明了您希望如何使用intoSet。在大多数情况下,我们只关心输入和输出是什么--因为输入是我们需要传入的类型,输出是我们最终将使用的类型。这两种类型告诉您有关收集器将如何与其余代码交互的关键信息。因此,您可能需要/必须指定它们。
Collector<Widget, TreeSet<Widget>, ?> intoSet是合法的,但在实践中可能用处不大。这是因为当您想要使用intoSet时,您不知道结果容器的类型(因此不能有意义地使用),只看声明即可。
类似的例子:
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>https://stackoverflow.com/questions/40077523
复制相似问题