Java PECS 原则详解

Last Modified: 2022/10/27

前言

想必同学们在 Java 搬砖过程中或多或少见到 <? extends T><? super T>。有些同学可能选择直接忽略,有些同学可能眉头一紧,一番搜索来到了这里。

理解 <? extends T>

我们将<? extends T>分解来看,首先看到是一对尖括号,表示泛型。紧接着是‘?‘,表示一种类型不确定的类型,然后是extends T,我们知道 extends 关键字在 Java 中表示继承,? extends T 表示任何一种继承于 T 的类型

总结一下:? extends T 表示 T 的任何子类(包括 T 类型本身)。如果我们拿 List<? extends Number> 作为例子,那么以下赋值都是合规的:

// 这里的 NUmber 就是上面的 T
List<? extends Number> foo3 = new ArrayList<Number>();  // 上面说到 T 本身是可以的,所以赋值合规
List<? extends Number> foo3 = new ArrayList<Integer>(); // Integer 继承自 Number,所以赋值合规
List<? extends Number> foo3 = new ArrayList<Double>();  // Double 继承自 Number,所以赋值合规

现在我们聚焦下 List<? extends Number> 类型变量 foo3 的读和写:

<? extends Number>的含义,我们知道,foo3 中某个对象 ‘o‘ 可能是 Number/Integer/Double 中的任意一种,但是具体是哪种,我们并不知道,但必定是 Number 或者 Number 的子类,因此我们可以通过 Number 类型的变量来引用该对象。

// 总是可以通过 Number 类型引用读出的对象,因为 get 出来的对象的类型一定是 T 或 T 的子类
Number n = foo3.get(i);

看下面的例子,我们尝试一下往 foo3 中写入,却得到 compile error。

foo3.add(new Integer(128)); // compile error
foo3.add(new Double(1.0)); // compile error

为啥会出现 compile error 呢?因为 foo3 引用的可能是 new ArrayList<Integer>(),但也可能是 new ArrayList<Integer>()。不能确定具体是哪种类型,所以不能写入。

总结

List<? extends T> 类型的 list 特性是可读不可写,限定读出对象的类型为 T 或者 T 的子类。因为可读,所以可以认为 list 在这里的角色是生产者(读 list 的一方是消费者,所以相对而言 list 就是生产者),即 producer。作为 producer 使用时,用 extends 关键字,简记为 ‘PE‘。

理解 <? super T>

我们将<? super T>分解来看,首先看到是一对尖括号,表示泛型。紧接着是‘?‘,表示一种类型不确定的类型,然后是super T,我们知道 super 关键字在 Java 中表示超类,? super T 表示任何 T 的超类

总结一下:? super T 表示 T 的任何超类(包括 T 类型本身)。如果我们拿 List<? super Integer> 作为例子,那么以下赋值都是合规的:

List<? super Integer> foo3 = new ArrayList<Integer>();  // 情况1:上面说到 T 本身是可以的,所以赋值合规
List<? super Integer> foo3 = new ArrayList<Number>();   // 情况2:Number 是 Integer 的超类,因此赋值合规
List<? super Integer> foo3 = new ArrayList<Object>();   // 情况3:Object 是 Integer 的超类,因此赋值合规

现在我们聚焦下 List<? super Integer> 类型变量 foo3 的读和写:

我们尝试一下从 foo3 读一个对象,读出的对象的类型显然是不确定的,可能是 Integer,可能是 Number,也可能是 Object。

我们需要注意,赋值合规,不代表写入 Integer/Number/Object 都是合法的。因为任一时刻,foo3 只会引用其中一种:

foo3.add(1); // 合法,不论实际引用是以三种情况中的哪一种,都是可以的,因为‘1’是整形,Integer 是 Number 和 Object 的子类。
foo3.add(new Double(1.0)); // 不合法,万一 foo3 引用的是第一种情况,我们显然不能将 Double 类型加到 `ArrayList<Integer>()`。

总结

List<? super T> 类型的 list 特性是限定写入对象的类型为 T 以及 T 的子类,可以认为 list 在这里的角色是消费者(调用 list.add 的一方是生产者,所以相对而言 list 就是消费者),即 consumer。作为 consumer 使用时,用 super 关键字,简记为 ‘CS‘。

PECS

Producer Extends, Consumer Super 首字母的缩写。

  • <? extends T> 限定读出对象的类型为 T 或者 T 的子类,可读不可写;
  • <? super T> 限定写入对象的类型为 T 以及 T 的子类。

一个同时用到 extends 和 super的例子:

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

参考文章

What is the difference between List<? super T> and List<? extends T> ?

有问题吗?点此反馈!

温馨提示:反馈需要登录