【问题标题】:PECS For generics in Non CollectionsPECS 用于非集合中的泛型
【发布时间】:2019-02-07 14:45:10
【问题描述】:

Joshua Bloch 提出了 PECS,其中规定了何时使用 ? extends T? super T。如果您从 Collections 框架的角度来考虑 PECS,那么它非常简单。如果向数据结构添加值,请使用? super T。如果从数据结构中读取,请使用? extends T。 例如:

public class Collections {  
    public static <T> void copy(List<? super T> dest, List<? extends T> src) {  
        for (int i = 0; i < src.size(); i++)   
            dest.set(i, src.get(i));   
    }   
}

如果我检查签名

public static <T> void sort(List<T> list, Comparator<? super T> c) 

我看到 Comparator 使用? super,所以它应该是一个消费者。看代码,比较器c只用于产生东西,因为它被问到比较的逻辑。

一方面,我理解为什么它是超类,因为作为开发人员,我想使用T 类的比较器以及T 超类的比较器,因为T 的对象也是超类的类型T。但是当我尝试从 PECS 的角度思考时,我无法理解。

PECS 是否仅适用于 Collections 框架?如果没有,有人可以向我解释一下比较器在Collections.sort 中消耗了什么吗?

【问题讨论】:

    标签: java generics collections bounded-wildcard


    【解决方案1】:

    为了这个答案,让我们以Comparator 作为主要的指导示例。

    如果您仔细考虑一下,您会发现Comparator 实际上接收两个T 类型的参数并返回它们的比较结果(由int 表示)。换句话说,它消耗两个T类型的实例,并产生一个int值。因此,根据 PECS 规则,它是T消费者,因此使用? super T

    更一般地,您应该从主类型的角度考虑 producerconsumer 的每个泛型参数的类型。如果某些Comparator 类型消耗 类型为T 的对象,PECS 规则规定此类Comparator&lt;T&gt; 的用户可以使用它来比较类型为T 子类型的对象。

    作为一个具体的例子,如果你碰巧已经有比较两个通用 Number 实例的逻辑(不管它们的具体类型是什么),你可以使用它来比较 Double 实例,因为双精度毕竟是数字。

    考虑以下比较器:

    Comparator<Number> c = Comparator.comparingInt(Number::intValue);
    

    这里,比较器c 比较Number 实例(任何 个),只考虑它们的组成部分。

    如果您有以下Double 实例列表:

    List<Double> doubles = Arrays.asList(2.2, 2.1, 7.3, 0.2, 8.4, 9.5, 3.8);
    

    还有下面的sort方法:

    static <T> void sort(List<T> list, Comparator<T> c) {
        list.sort(c);
    }
    

    (注意Comparator 参数中没有通配符? super T)。

    然后,如果要对List&lt;Double&gt; doubles 列表进行排序,上述sort 方法的签名将要求您传递具体的Comparator&lt;Double&gt;。但是,如果您想使用之前定义的c 比较器对List&lt;Double&gt; doubles 进行排序怎么办?

    由于比较器的类型是Comparator&lt;Number&gt;,而doubles 列表的类型是List&lt;Double&gt;,因此以下代码会产生编译错误:

    sort(doubles, c);
    

    幸运的是,由于Comparator 是它所比较的​​元素类型的消费者,您可以将sort 方法的签名更改为:

    static <T> void sort(List<T> list, Comparator<? super T> c) {
        list.sort(c);
    }
    

    现在,这段代码可以编译了:

    sort(doubles, c);
    

    【讨论】:

    • 非常感谢您超级干净的解释。我想用 Iterator 再举一个例子。如果我设计一个调用 T next() 的方法,那么我应该用 ?扩展因为Iterator现在产生T。它可以被概括为好像方法中的泛型类消耗T(就像在比较器的情况下)它应该是超级的,如果它返回对T的引用(就像在迭代器的情况下)那么它应该是扩展的。
    • @FatihArslan 是的,没错。例如,如果您有一个接收Iterator&lt;Number&gt; 的方法并且您尝试传递Iterator&lt;Integer&gt;,它将无法编译。但是,如果您更改方法,使其现在收到Iterator&lt;? extends Number&gt;,您将能够毫无问题地传递Iterator&lt;Integer&gt;。通配符也可以混合使用。例如,Stream.map 方法接收到 Function&lt;? super T, ? extends R&gt;
    猜你喜欢
    • 1970-01-01
    • 2012-08-25
    • 2021-12-04
    • 1970-01-01
    • 2012-11-22
    • 2013-11-19
    • 1970-01-01
    • 2012-09-16
    • 1970-01-01
    相关资源
    最近更新 更多