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

标签 java generics wildcard

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

环顾四周时,我遇到了一些我不明白的事情。考虑以下来自 Java 源代码的代码(稍微重新格式化):

public static <T> Collector<T, ?, List<T>> toList() {
    return new Collectors.CollectorImpl<>(
        (Supplier<List<T>>) ArrayList::new,
        List::add,
        (left, right) -> {
             left.addAll(right);
             return left;
        },
        Collectors.CH_ID
    );
}

为什么 toList 方法签名需要在其返回类型中使用通配符 ??当我删除它时,我得到

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>

当我将 ? 更改为 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)

进一步摆弄并没有教会我更多东西。

我理解在一种情况下,例如 ? extends T,Java 中的通配符可以转换为 C# 作为新的泛型类型参数,带有 where TWildCard : T。但是上面的 toList 是怎么回事,返回类型只有一个通配符?

最佳答案

Collection 家 have three type parameters :

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 operation

对于一些 Collection 家,比如toList , A 的类型和 R是一样的,因为结果本身就是用来累加的。

toList返回的收集器的实际类型将是 Collector<T, List<T>, List<T>> .

(收集器的一个例子是 Collectors.joining() uses a StringBuilder 。)

A 的类型参数大多数时候是通配符,因为我们通常不关心它到底是什么。它的实际类型仅供收集器内部使用,we can capture it if we need to refer to it by a name :

// 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);
}

您还可以在代码中看到 toList它指定 Collectors.CH_ID 作为其特征,它指定了身份完成。这意味着它的终结器除了返回传递给它的任何内容外什么都不做。


(我在下面的评论中引用了此部分。)

这里有几个替代设计来为累加器携带类型参数。我认为这些说明了为什么 Collector 的实际设计类(class)很好。

  1. 只需使用 Object , 但我们最终投了很多。

    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);
    }
    
  2. 隐藏累加器作为 Collector 的实现细节, 作为 Collector本身在内部进行积累。我认为这可能是有道理的,但它不太灵活,并且组合器步骤变得更加复杂。

    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);
            }
        };
    }
    

关于java - 为什么 Java Collector.toList() 需要在其返回类型中使用通配符类型占位符?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/50538364/

相关文章:

algorithm - 使用通配符 (GLOB) 支持搜索数百万个文件名的更好方法是什么

java - 如何使用 ViewPorts 为 libgdx 中的不同分辨率缩放 ImageButtons?

java - 这是什么意思 "No DRP key due to exception:java.lang.ClassNotFoundException: com.android.webview.chromium.Drp"?

java - 类型参数名称 P 和 R 的含义是什么?它们如何使用?

Linux 查找命令

ssl - 通配符 SSL 证书、自定义域名和 Cloudflare

java - 将文件名放在括号内

java - 这是优化我的网络模型的更有效方法吗?

java - 如何在 java 的方法中使用泛型 T?

ios - 如何将符合具有关联类型的协议(protocol)的不同类型添加到集合中?