我在阅读泛型时遇到了PECS(Producer extends和Consumer super的缩写)。

有人能向我解释一下如何使用PECS来解决extends和super之间的混淆吗?


当前回答

tl;博士:“PECS”是从藏品的角度来看的。如果您只是从泛型集合中提取项,那么它是一个生产者,您应该使用extends;如果你只是在填充物品,那么它是一个消费者,你应该使用super。如果同时使用同一集合,则不应使用extends或super。


假设您有一个方法,它以一个集合作为参数,但您希望它比只接受集合<Thing>更灵活。

案例1:你想通过收集并处理每个项目。那么列表是一个生产者,因此您应该使用集合<?扩展Thing>。

理由是集合<?extendsThing>可以保存Thing的任何子类型,因此当您执行操作时,每个元素都将表现为Thing。(实际上不能向集合中添加任何内容(null除外)<?扩展Thing>,因为您无法在运行时知道集合持有Thing的哪个特定子类型。)

案例2:您想要向集合中添加内容。那么列表是消费者,因此您应该使用集合<?超级事物>。

这里的理由是,不同于集合<?扩展Thing>,Collection<?super Thing>可以始终保存Thing,无论实际参数化类型是什么。在这里,只要它允许添加Thing,您就不在乎列表中已经存在什么;这是什么?super Thing保证。

其他回答

public class Test {

    public class A {}

    public class B extends A {}

    public class C extends B {}

    public void testCoVariance(List<? extends B> myBlist) {
        B b = new B();
        C c = new C();
        myBlist.add(b); // does not compile
        myBlist.add(c); // does not compile
        A a = myBlist.get(0); 
    }

    public void testContraVariance(List<? super B> myBlist) {
        B b = new B();
        C c = new C();
        myBlist.add(b);
        myBlist.add(c);
        A a = myBlist.get(0); // does not compile
    }
}

tl;博士:“PECS”是从藏品的角度来看的。如果您只是从泛型集合中提取项,那么它是一个生产者,您应该使用extends;如果你只是在填充物品,那么它是一个消费者,你应该使用super。如果同时使用同一集合,则不应使用extends或super。


假设您有一个方法,它以一个集合作为参数,但您希望它比只接受集合<Thing>更灵活。

案例1:你想通过收集并处理每个项目。那么列表是一个生产者,因此您应该使用集合<?扩展Thing>。

理由是集合<?extendsThing>可以保存Thing的任何子类型,因此当您执行操作时,每个元素都将表现为Thing。(实际上不能向集合中添加任何内容(null除外)<?扩展Thing>,因为您无法在运行时知道集合持有Thing的哪个特定子类型。)

案例2:您想要向集合中添加内容。那么列表是消费者,因此您应该使用集合<?超级事物>。

这里的理由是,不同于集合<?扩展Thing>,Collection<?super Thing>可以始终保存Thing,无论实际参数化类型是什么。在这里,只要它允许添加Thing,您就不在乎列表中已经存在什么;这是什么?super Thing保证。

协方差:接受子类型相反:接受超类型

协变类型是只读的,而逆变类型是只写的。

PECS(生产者延伸和消费者超级)

[协方差和反方差]

让我们来看看示例

public class A { }
//B is A
public class B extends A { }
//C is A
public class C extends A { }

泛型允许您以安全的方式动态处理类型

//ListA
List<A> listA = new ArrayList<A>();

//add
listA.add(new A());
listA.add(new B());
listA.add(new C());

//get
A a0 = listA.get(0);
A a1 = listA.get(1);
A a2 = listA.get(2);
//ListB
List<B> listB = new ArrayList<B>();

//add
listB.add(new B());

//get
B b0 = listB.get(0);

问题

由于Java的Collection是一种引用类型,因此我们面临以下问题:

问题#1

//not compiled
//danger of **adding** non-B objects using listA reference
listA = listB;

*Swift的泛型没有这样的问题,因为Collection是Value类型[About],因此创建了一个新的集合

问题#2

//not compiled
//danger of **getting** non-B objects using listB reference
listB = listA;

解决方案-通用通配符

通配符是引用类型功能,不能直接实例化

解决方案#1<? super A>又称下界,又称反方差,又称消费者保证它由A和所有超类操作,这就是为什么添加

List<? super A> listSuperA;
listSuperA = listA;
listSuperA = new ArrayList<Object>();

//add
listSuperA.add(new A());
listSuperA.add(new B());

//get
Object o0 = listSuperA.get(0);

解决方案#2

<? 扩展A>aka上界aka协方差aka生产者保证它由A和所有子类操作,这就是为什么它是安全的

List<? extends A> listExtendsA;
listExtendsA = listA;
listExtendsA = listB;

//get
A a0 = listExtendsA.get(0);

PECS:生产者延伸和消费者超级

理解的前提条件:

泛型和泛型通配符多态性、亚型和超型


假设我们有一个采用泛型类型参数T的类型,例如List<T>。当我们编写代码时,还允许泛型类型参数T的子类型或超类型可能是有益的。这放松了对API用户的限制,可以使代码更加灵活。

让我们先看看放松这些限制会带来什么。假设我们有以下3个类:

class BaseAnimal{};

class Animal extends BaseAnimal{};

class Duck extends Animal{};

我们正在构建一个公共方法,该方法采用列表<Animal>

如果我们使用超级列表<?superAnimal>而不是List<Animal>,我们现在可以传递更多的列表来满足我们方法的要求。我们现在可以传入List<Animal>或List<BaseAnimal>甚至List<Object>如果我们使用扩展列表<?扩展Animal>而不是List<Animal>,我们现在可以传递更多的列表来满足我们方法的要求。我们现在可以传入List<Animal>或List<Duck>

然而,这存在以下两个限制:

如果我们使用像List<?super Animal>我们不知道List<t>的确切类型。它可能是List<Animal>或List<BaseAnimal>或List<Object>的列表。我们无从得知。这意味着我们永远无法从列表中获取值,因为我们不知道该类型是什么。但是,我们可以将任何Animal数据类型或将其扩展到列表中。因为我们只能将数据放入列表,所以它被称为数据消费者。如果我们使用扩展列表<?扩展Animal>而不是List<Animal>。我们也不知道确切的类型是什么。它可以是List<Animal>或List<Duck>。我们现在不能在列表中添加一些东西,因为我们永远无法确定是什么类型。但是我们可以删除一些东西,原因是我们始终知道列表中的任何东西都是Animal的子类型。因为我们只能从列表中提取数据,所以它被称为数据的生产者。

这里有一个简单的程序来说明类型限制的放松:

import java.util.ArrayList;
import java.util.List;

public class Generics {
    public static void main(String[] args) {

        Generics generics = new Generics();

        generics.producerExtends(new ArrayList<Duck>());
        generics.producerExtends(new ArrayList<Animal>());

        generics.consumerSuper(new ArrayList<Object>());
        generics.consumerSuper(new ArrayList<Animal>());

    }

    //  ? extends T   is an upper bound
    public void producerExtends (List<? extends Animal> list) {

        // Following are illegal since we never know exactly what type the list will be
        // list.add(new Duck());
        // list.add(new Animal());
        
        // We can read from it since we are always getting an Animal or subclass from it
        // However we can read them as an animal type, so this compiles fine
        if (list.size() > 0) {
            Animal animal = list.get(0);
        }
    }

    // ? extends T   is a lower bound
    public void consumerSuper (List<? super Animal> list) {
        // It will be either a list of Animal or a superclass of it
        // Therefore we can add any type which extends animals
        list.add(new Duck());
        list.add(new Animal());

        // Compiler won't allow this it could potentially be a super type of Animal
        // Animal animal = list.get(0);
    }