Java中泛型extends与super的区别

从泛型擦除说起

1.Java从1.5开始引入了泛型,泛型-也就是参数化类型。在Java容器类中大量的使用了泛型这种思想。泛型的利用好处很多:

减少了代码中大量的强制性类型转换,可读性更高
通用性更强,
类型更安全,提高性能等

2.需要明确的是Java的泛型是伪泛型,泛型只在编译时期有效,在编译器正确的检验出泛型的结果后随即泛型的信息就会被擦除。并且泛型的信息不会进入到运行时阶段。看一个栗子:

1
2
3
4
5
6
7
8
9
10
11
public static void main(String[] args) {

List<Integer> integers = new ArrayList<>();
integers.add(1);

List<String> strings = new ArrayList<>();
strings.add("1");

System.out.println(integers.getClass().equals(strings.getClass()));
}
true

3.泛型信息被擦除成原始的List,在实际开发中,泛型擦除会带来一些问题,既然运用了无界泛型,并且由于泛型擦除的存在,那么只能调用Object的自身方法。这个时候我们就需要给泛型指定边界,将泛型限制在一定的范围。

上界通配符<? extends T>

1.上界,顾名思义,最高只能到T,也即类型只能是T或者是T的子类。以水果这个常见的类型举例,水果,热带水果,苹果,橘子,香蕉等。可见水果作为整个超类依次往下。

1
2
3
4
5
6
7
8
9
10
11
public class Fruit {}

public class TropicalFruit extends Fruit {/** 热带水果 */}

public class Apple extends TropicalFruit {}

public class RedApple extends Apple {}

public class Orange extends TropicalFruit {}

public class Banana extends TropicalFruit {}

2.拿集合List举例:

1
2
3
4
5
6
public static void main(String[] args) {
List<? extends TropicalFruit> appleList = new ArrayList<>();
//报错,提示无法添加
appleList.add(new Apple());
appleList.add(null);
}

3.之前提到<? extends T>上界通配符规定的是最高到T类型,而我们这里List<? extends TropicalFruit>为什么不能够添加Apple呢?null是可以的,因为null表示任何类型。这里定义了一个装热带水果(TropicalFruit)的集合,按理说苹果(Apple)是可以放进去的,这是对于概念混淆可能会犯这样的错误。其实这里很好理解,首先编译器其实不聪明,List<? extends TropicalFruit>编译器理解的是,只要是热带水果本身或者其子类都是可以存放的,那么问题来了,假如是一个List<Apple>,此时我想放香蕉那显然是不行的。类型转化错误,但是编译器知道的是都是热带水果TropicalFruit,有可能是装苹果的袋子,也有可能是装香蕉的袋子,具体是不知道的,那么添加操作显然是不安全的。索性编译时期直接报错。

下界通配符<? super T>

1.同样的:

1
2
3
4
5
6
7
8
public static void main(String[] args) {
List<? super Apple> appleList = new ArrayList<>();
appleList.add(new Apple());
appleList.add(new RedApple());
appleList.add(null);
//报错,无法通过编译
appleList.add(new TropicalFruit());
}

2.还是一样的问题,既然规定存放的是Apple以及它的超类,那么TropicalFruit是符合要求的,为什么却无法添加?
3.类比上届通配符,<? super T>集合中保存的是T以及T的父类,编译器是不知道具体的类型的(有可能是直接父类,有可能是超类),有可能是List<Apple>List<Fruit>。例子中的代码

List<? super Apple> 存放的是Apple,那么Apple,RedApple都是可以添加的

这样可以添加的原因,想想多态的概念,向上转型是安全的操作(子类的对象赋值给父类的引用),存放的Apple可以向上转型为TropicalFruit、Fruit,Object,如果存入Fruit显然涉及向下强制类型转化,可是编译器判断不了,因为可能是List<Apple>或者List<TropicalFruit>,也即下界其实定义最小能添加的元素类型T或者T的子类(向上转型属于安全操作)。

4.extends可用于的返回类型限定,不能用于参数类型限定。super可用于参数类型限定,不能用于返回类型限定。

PECS原则

1.如果要从集合中读取类型T的数据,并且不能写入,可以使用 ? extends 通配符;(Producer Extends->PE)
2.如果要从集合中写入类型T的数据,并且不需要读取,可以使用 ? super 通配符;(Consumer Super->CS)
3.如果既要存又要取,那么就不要使用任何通配符。

这个功能是摆设,看看就好~~~