为什么Java Collector.toList()在其返回类型中需要通配符类型占位符?

为什么Java Collector.toList()在其返回类型中需要通配符类型占位符?,java,generics,wildcard,Java,Generics,Wildcard,我一直在做一些Java流操作,当然它不喜欢我的代码,并且拒绝提供有用的错误消息。(作为参考,我对C#和Linq没有任何问题,所以我从概念上理解了我试图做的一切。)因此我开始深入研究向代码中的每个方法添加显式泛型类型,以便找到问题的根源,因为过去的经验告诉我,这是一条成功的前进道路 环顾四周,我发现了一些我不明白的东西。从java源代码中考虑下面的代码(重新格式化一点): 及 当我放回通配符并检查这两个时,它们是: List–公共抽象布尔加法(te) 列表–公共抽象布尔addAll(集合收集器:

我一直在做一些Java流操作,当然它不喜欢我的代码,并且拒绝提供有用的错误消息。(作为参考,我对C#和Linq没有任何问题,所以我从概念上理解了我试图做的一切。)因此我开始深入研究向代码中的每个方法添加显式泛型类型,以便找到问题的根源,因为过去的经验告诉我,这是一条成功的前进道路

环顾四周,我发现了一些我不明白的东西。从java源代码中考虑下面的代码(重新格式化一点):

当我放回通配符并检查这两个时,它们是:

List–公共抽象布尔加法(te)
列表–公共抽象布尔addAll(集合收集器:

T
-还原操作的输入元素类型

A
-还原操作的可变累积类型(通常作为实现细节隐藏)

R
-还原操作的结果类型

对于某些收集器,例如
toList
A
R
的类型是相同的,因为结果本身用于累加

toList
返回的收集器的实际类型将是
collector

(收集器的一个示例是,其累积的类型与其结果不同,即which。)

A
的类型参数在大多数情况下都是通配符,因为我们通常不关心它实际上是什么。它的实际类型仅由收集器内部使用,并且:

  • 隐藏累加器作为
    收集器
    的一个实现细节,因为
    收集器
    本身在内部进行累加。我认为这是有道理的,但它的灵活性较低,合并器的步骤变得更复杂

    interface Collector<T, R> {
        void accumulate(T elem);
        void combine(Collector<T, R> that);
        R finish();
    }
    
    static <T> Collector<T, List<T>> toList() {
        return new Collector<T, List<T>>() {
            private List<T> list = new ArrayList<>();
            @Override
            public void accumulate(T elem) {
                list.add(elem);
            }
            @Override
            public void combine(Collector<T, List<T>> that) {
                // We could elide calling finish()
                // by using instanceof and casting.
                list.addAll(that.finish());
            }
            @Override
            public List<T> finish() {
                return new ArrayList<>(list);
            }
        };
    }
    
    接口收集器{
    空洞累积(T元素);
    真空联合收割机(收集器);
    R完成();
    }
    静态收集器toList(){
    返回新收集器(){
    私有列表=新的ArrayList();
    @凌驾
    公共空间累积(T元素){
    列表。添加(元素);
    }
    @凌驾
    公共无效联合收割机(该收割机){
    //我们可以省略调用finish()
    //通过使用instanceof和casting。
    list.addAll(that.finish());
    }
    @凌驾
    公共列表完成(){
    返回新的ArrayList(列表);
    }
    };
    }
    

  • 为了澄清,您正在询问返回类型
    收集器中的通配符,对吗?@Radiodef正确。澄清。非常好的问题,直到现在我才考虑。谢谢。链接非常好。在接受答案之前,我将研究一下。我真的想知道,通配符什么时候重要?为什么有通配符累加器类型是泛型类型参数的一部分吗?只要输出结果对输入正确,收集器方法的调用方如何关心累加器是什么?累加器有一个类型参数,以便API代码可以捕获和使用它,就像我在示例中使用的那样。例如,并行流收集le将涉及多个线程,每个线程都有自己的累加器对象,最后还有一个合并器步骤(我还为我的答案添加了一些示例,我认为这些示例有助于说明这一点)对于设计人员来说,为什么选择
    收集器的实际设计可能是一个有趣的问题。您的新更新成功了!我现在看到,虽然让收集器编写器成为实现细节没有内在问题,但通过让收集器编写器这样做,Java称之为收集器的收集器周围的不变量应该支持的可能不被支持。我仍在考虑这样做。但是为什么不将累加器类型参数指定为'List`?为什么需要通配符?除了更短之外,我认为它允许以某种方式更改特定收集器的实现,而不必担心破坏依赖它的代码。
    Collectors.joining()
    可以改为使用
    StringJoiner
    而不是
    StringBuilder
    。我想这可能就是官方API这么做的原因。(如果Stuart Marks看到问答会很好,因为他可能有什么要补充的。直接问他。)有趣。对我来说,这意味着收集器可能有两个版本。一个用于构造,如
    CollectorDefinition
    ,另一个用于使用,
    Collector
    ,系统接受一个并提供另一个,因此可以保证不变量。向cod公开泛型类型参数中的实现细节对我来说,不仅不关心而且不应该关心这些细节似乎是一种代码味道。
    Wrong number of type arguments: 2; required: 3
    
    Incompatible types.
    Required: Collector<T, List<T>, >
    Found: CollectorImpl<java.lang.Object, List<T>, java.lang.Object>
    
    List::add – Cannot resolve method 'add'
    left.addAll – Cannot resolve method 'addAll(java.lang.Object)'
    
    List – public abstract boolean add(T e)
    List – public abstract boolean addAll(Collection<? extends T> c)
    
    // Example of using a collector.
    // (No reason to actually write this code, of course.)
    public static <T, R> collect(Stream<T> stream,
                                 Collector<T, ?, R> c) {
        return captureAndCollect(stream, c);
    }
    private static <T, A, R> captureAndCollect(Stream<T> stream,
                                               Collector<T, A, R> c) {
        // Create a new A, whatever that is.
        A a = c.supplier().get();
    
        // Pass the A to the accumulator along with each element.
        stream.forEach(elem -> c.accumulator().accept(a, elem));
    
        // (We might use combiner() for e.g. parallel collection.)
    
        // Pass the A to the finisher, which turns it in to a result.
        return c.finisher().apply(a);
    }
    
    interface Collector<T, R> {
        Supplier<Object> supplier();
        BiConsumer<Object, T> accumulator();
        BiFunction<Object, Object, Object> combiner();
        Function<Object, R> finisher();
    }
    
    static <T> Collector<T, List<T>> toList() {
        return Collector.of(
            ArrayList::new,
            (obj, elem) -> ((List<T>) obj).add(elem),
            (a, b) -> {
                ((List<T>) a).addAll((List<T>) b);
                return a;
            },
            obj -> (List<T>) obj);
    }
    
    interface Collector<T, R> {
        void accumulate(T elem);
        void combine(Collector<T, R> that);
        R finish();
    }
    
    static <T> Collector<T, List<T>> toList() {
        return new Collector<T, List<T>>() {
            private List<T> list = new ArrayList<>();
            @Override
            public void accumulate(T elem) {
                list.add(elem);
            }
            @Override
            public void combine(Collector<T, List<T>> that) {
                // We could elide calling finish()
                // by using instanceof and casting.
                list.addAll(that.finish());
            }
            @Override
            public List<T> finish() {
                return new ArrayList<>(list);
            }
        };
    }