从泛型擦除说起
1.Java
从1.5开始引入了泛型,泛型-也就是参数化类型。在Java
容器类中大量的使用了泛型这种思想。泛型的利用好处很多:
减少了代码中大量的强制性类型转换,可读性更高
通用性更强,
类型更安全,提高性能等
2.需要明确的是Java
的泛型是伪泛型,泛型只在编译时期有效,在编译器正确的检验出泛型的结果后随即泛型的信息就会被擦除。并且泛型的信息不会进入到运行时阶段。看一个栗子:1
2
3
4
5
6
7
8
9
10
11public 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
11public 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
6public 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
8public 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.如果既要存又要取,那么就不要使用任何通配符。