泛型与 PECS 原则

2,795 阅读3分钟

Producer Extends Consumer Super.
生产者Extends,消费者Super.

测试

类继承结构:
Executive(经理) extends Manager(管理者) extends Employee(职工) extends Object

还有一个测试的泛型类.

    static class Employee {

    }
    static class Manager extends Employee {

    }

    static class Executive extends Manager {

    }
    static class Test<T> {
        T value;

        public void setValue(T value) {
            this.value = value;
        }
        public T getValue() {
            return value;
        }
    }

你能理解下面的代码吗

? extends T 初始化

//      Test<? extends Manager> t4= new Test<Employee>(); // 报错
//      Test<? extends Manager> t5 = new Test<Object>(); // 报错
        Test<? extends Manager> t6 = new Test<Manager>();
        Test<? extends Manager> t7 = new Test<Executive>();

? super T 初始化

//      Test<? super Manager> t1 = new Test<Executive>(); // 报错
        Test<? super Manager> t2 = new Test<Manager>();
        Test<? super Manager> t3 = new Test<Object>();
        Test<? super Manager> t4 = new Test<Employee>();
				

可以看出,? extend T 和 ? super T 限制了可以声明类型的允许范围.以Manager为界限,? extend T 可以声明Manager以及Manager以下, ? super T可以声明Manager以及Manager以上.

? extends T

描述一个这样的对象/容器对象,它/它内部的对象起码是个T,你对T的所有期望都可以满足.可以做到T可以做的所有事.
因此我们可以get(),无论容器中是T的哪个子类或者T本身,我们都可以当作T来操作.
这也就是生产者Extends,它可以对外提供对象.
但你不知道T实际是什么,是哪个子类.所以你的任何set操作,或者容器的add操作都不被java允许.否则就可能出现一个装香蕉的水果篮子里出现了苹果的情况.

           Test<? extends Manager> t7 = new Test<Executive>();
//        t7.setValue(new Employee());  // 报错
//        t7.setValue(new Manager()); // 报错
//        t7.setValue(new Executive()); // 报错
//        t7.setValue(new Object()); // 报错

令人迷惑的 ? super T

或许你以为,按照对称性, ? super T描述的应该是一个什么都可以放的对象/容器,只要是T的父类.包括Object.
以下语句也确实不会报错.

        Test<? super Manager> t2 = new Test<Manager>();
        Test<? super Manager> t3 = new Test<Object>();
        Test<? super Manager> t4 = new Test<Employee>();

但是你会失望的发现.在执行set操作/容器的add操作的时候.

        Test<? super Manager> t4 = new Test<Employee>();
        t4.setValue(new Manager());
        t4.setValue(new Executive());
//        t4.setValue(new Object());    //报错
//        t4.setValue(new Employee());  //报错

你只能set/add Manager和Manager的子类. 这是因为:
? super T 确实描述了一个T的父类对象/父类容器,但不知道是哪个父类,所以你只能set T/T的子类. 因为,所有T的子类都可以转化为任何一个T的父类. set/add 操作是绝对安全的. 这也就是消费者 Super. 可以接受对象.

但由于你不清楚实际是那个父类,所以你只能get到一个Object.
Object o = t4.getValue(); 这是合法的,但是没有什么意义.Object对象除了一个引用,无法进行什么操作.

容器操作

这方面的例子可以看awesomehuan.com/2016/03/05/…加深印象.

巧妙的运用

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

上面代码中copy方法的功能是将src中的数据复制到dest中,这里src就是生产者,dest就是消费者。 设计这样的方法,好处就是,可以复制任意类型的List,通用性特别强。

参考

stackoverflow.com/questions/2…

awesomehuan.com/2016/03/05/…