一 前述

生活中的例子:中药店里面每个抽屉都贴着标签;实验室的瓶子有标签写着存放什么药物。

1.1 背景

1)集合容器类在定义声明阶段不能确定这个容器存放的是什么类型的对象,所以在jdk5的时候,增加泛型来解决。
2)因为这个时候除了元素的类型不确定,其他的部分是确定的,例如关于这个元素如何保存,如何管理等是确定的,因此此时把元素的类型设计成一个参数,这个类型参数叫做泛型。
3)Collection,List,ArrayList这个就是类型参数,即泛型。
4)所谓泛型,就是允许在定义类、接口时通过一个标识表示类中某个属性的类型或者是某个方法的返回值及参数类型。
5)这个类型参数将在使用时(例如,继承或实现这个接口,用这个类型声明变量、创建对象时)确定(即传入实际的类型参数,也称为类型实参)。

1.2 泛型工作原理

1)泛型是通过类型擦除来实现的,编译器在编译时擦除了所有类型相关的信息,所以在运行时不存在任何类型相关的信息。
2)当泛型被擦除后,他有两种转换方式,第一种是如果泛型没有设置类型上限,那么将泛型转化成Object类型,第二种是如果设置了类型上限,那么将泛型转化成他的类型上限。这样做的目的,是确保能和Java 5之前的版本开发二进制类库进行兼容。你无法在运行时访问到类型参数,因为编译器已经把泛型类型转换成了原始类型。
3)通过extends关键字方式实现设置类型上限。

二 泛型在集合中的应用

1)集合相关的api在声明时,使用了泛型。
2)实例化集合对象时,如果没有使用泛型。则默认操作的是java.lang.Object类型。
3)实例化集合对象时,如果使用了泛型,则集合中就只能操作确定类型的数据。

  1. List<String> list = new ArrayList<String>();
  2. list.add(String str);

4)泛型参数,只能使用引用数据类型来充当。不能使用基本数据类型。

三 定义泛型结构

3.1 定义泛型类

  1. public class Order<T> {
  2. private int orderId;
  3. private String orderName;
  4. private T[] arr;
  5. private T orderT;
  6. public Order(){
  7. // arr = new T[12]; 错误的
  8. arr = (T[]) new Object[12]; // 正确的
  9. }
  10. }

使用泛型注意事项:
1)如果继承时,没有父类的泛型参数的类型,则默认此类型为Object

  1. // 这种情况下,父类Order是泛型类,而子类没有显式指定泛型参数类型,所以默认为Object类
  2. public class SubOrder extends Order {
  3. }

2)继承泛型类时,可以指明泛型参数的类型。子类不是泛型类

  1. // 此时,显示指定类Order的泛型参数为String类型,子类不是一个泛型类
  2. public class SubOrder1 extends Order<String> {
  3. // SubOrder1:不是一个泛型类!
  4. }

3)继承泛型类的时候,也可以不指定泛型参数类型,直接使用 T 将子类也定义成一个泛型类

  1. // 此时,不显示指定父类Order的泛型
  2. public class SubOrder2<T> extends Order<T> {
  3. // 子类SubOrder2仍然是一个泛型类
  4. }

4)指定泛型参数类型时,只能是引用类型,不能是基本数据类型
5)指定泛型参数类型的场景:
①子类继承泛型父类,显式的指定泛型参数的类型
②在实例化泛型类的时候,声明变量的时候指定泛型参数

3.2 泛型类说明

1)泛型类可能有多个参数,此时应将多个参数一起放在尖括号内。比如:
2)泛型类的构造器如下:

  1. public GenericClass(){}
  2. // 错误示例如下,不要加泛型参数
  3. public GenericClass<E>(){}

3)实例化泛型类后,操作原来泛型位置的结构必须与指定的泛型类型一致。
4)泛型不同的引用不能相互赋值,尽管在编译时ArrayList和ArrayList是两种类型,但是,在运行时只有一个ArrayList被加载到JVM中。
5)在创建泛型类对象的时候如果不指定,将被擦除,泛型对应的类型均按照Object处理,但不等价于Object。经验:泛型要使用一路都用。要不用,一路都不要用。
6)如果泛型结构是一个接口或抽象类,则不可创建泛型类的对象。
7)jdk1.7,泛型的简化操作:new后面的泛型类不需要指定泛型类型。

  1. ArrayList<Fruit> flist = new ArrayList<>();

8)泛型的指定中不能使用基本数据类型,可以使用包装类替换。
9)在类/接口上声明的泛型,在本类或本接口中即代表某种类型,可以作为非静态属性的类型、非静态方法的参数类型、非静态方法的返回值类型。但在静态方法中不能使用类的泛型。
10)异常类不能是泛型的。
11)不能使用new E[ ]。但是可以:E[] elements = (E[])new Object[capacity]参考:ArrayList源码中声明:Object[] elementData,而非泛型参数类型数组。
12)泛型接口的定义和泛型类类似。

3.3 泛型方法

1)泛型方法所属的类可以是泛型类,也可以不是泛型类。
2)当调用泛型方法的时候,指明泛型方法的泛型参数类型。
3)泛型方法可以是静态的。

  1. // 泛型方法,第一个<E>是声明泛型方法中的E类型,第二个<E>是返回值List中的元素类型为指定的泛型
  2. public <E> List<E> copyArrayToList(E[] arr){
  3. ArrayList<E> list = new ArrayList<>();
  4. for(E e : arr){
  5. list.add(e);
  6. }
  7. return list;
  8. }

四 泛型在继承中的实现

情况一,类A是类B的父类,但是 G 与 G没有继承关系。 情况二,类A是类B的父类或接口,A 是 B的父类,可以向上转型(多态)

4.1 继承关系

1)使用关键字 extends 来限制泛型的上限

  1. // 泛型没有上限,默认是Object
  2. public class TestGeneric<T> {
  3. }
  4. // 泛型上限是List,传入的泛型只能是List或者子实现类
  5. public class TestGeneric<T extends List> {
  6. }

4.2 泛型之间的依赖关系

1)当传入两个泛型时,可以制定两个泛型之间的联系,如 A 只能是 B 的子类

  1. // A只能是B的子类或者和B相同类型
  2. public <B, A extends B> void fun(A a, B b) {
  3. System.out.println("a => " + a);
  4. System.out.println("b => " + b);
  5. }
  6. @Test
  7. public void test1() {
  8. fun("A", "B");
  9. }

五 通配符

5.1 通配符:<?>

表示不确定的泛型类型,无限通配符,也可表示不关心实际的操作类型

1)使用通配符,如:List<?>,Map<?, ?>。List<?> 是 List、List 等各种泛型List的父类。
2)读取List<?>的对象list中的元素时,永远是安全的,因为不管list的真实类型是什么,它包含的都是Object。
3)需要注意的是:无限通配符只有读的能力,没有写的能力。

  1. public void testV(List<?> list) {
  2. // 编译器不允许该操作
  3. // list.add("jaljal");
  4. Object o = list.get(0);
  5. }

5.2 使用通配符

1)将任意元素加入到使用通配符的泛型类的对象中,不是类型安全的。因为我们不知道c的元素类型,我们不能向其中添加对象。add方法有类型参数E作为集合的元素类型。我们传给add的任何参数都必须是一个未知类型的子类。因为我们不知道那是什么类型,所以我们无法传任何东西进去。

  1. Collection<?> c = new ArrayList<String>();
  2. c.add(new Object()); // 编译时错误

2)唯一的例外是 null ,他是所有类型的成员。
3)可以使用get() 方法并使用其返回值。返回值是一个未知类型,但是他肯定是一个Object。

  • 通配符使用案例:

    1. public static void main(String[] args) {
    2. List<?> list = null;
    3. list = new ArrayList<String>();
    4. list = new ArrayList<Double>();
    5. // list.add(3); // 编译不通过
    6. list.add(null);
    7. List<String> l1 = new ArrayList<String>();
    8. List<Integer> l2 = new ArrayList<Integer>();
    9. l1.add("test");
    10. l2.add(15);
    11. read(l1);
    12. read(l2);
    13. }
    14. public static void read(List<?> list) {
    15. for (Object o : list) {
    16. System.out.println(o);
    17. }
    18. }

    5.2.1 注意事项

    1)不能用在泛型方法声明上,返回值类型前面的<>不能使用?

    1. // 编译错误
    2. public static <?> void test(ArrayList<?> list){
    3. }

    2)不能用在泛型类的声明上

    1. // 编译错误
    2. class GenericTypeClass<?>{
    3. }

    3)不能用在创建对象上,右边是属于创建集合对象

    1. // 编译错误
    2. ArrayList<?> list2 = new ArrayList<?>();

    5.3 有限制的通配符

    5.3.1 <? extends T>

    1)通配符上限:上限extends:使用时指定的类型必须是继承某个类,或者实现某个接口,即 <=
    2)定义了泛型的上限,但是也只有读的能力。
    3)这种方式表示参数化的类型只能是T,或者是T的子类型

    1. // 只允许泛型为Number及Number子类的引用调用:(无穷小 , Number]
    2. <? extends Number>
    3. // 只允许泛型为实现Comparable接口的实现类的引用调用
    4. <? extends Comparable>
  • 实例

    1. public static void printCollection3(Collection<? extends Person> coll) {
    2. // Iterator只能用Iterator<?>或Iterator<? extends Person>.why?
    3. Iterator<?> iterator = coll.iterator();
    4. while (iterator.hasNext()) {
    5. System.out.println(iterator.next());
    6. }
    7. }

    5.3.2 <? super T>

    1)通配符下限:下限super:使用时指定的类型不能小于操作的类,即 >=
    2)定义泛型的下限,有读的能力,及部分写的能力
    3)泛型只能是T,或者T的父类型

    1. // 只允许泛型为Number及Number父类的引用调用,[Number , Object)
    2. <? super Number>
  • 实例

    1. public static void printCollection4(Collection<? super Person> coll) {
    2. // Iterator只能用Iterator<?>或Iterator<? super Person>.why?
    3. Iterator<?> iterator = coll.iterator();
    4. while (iterator.hasNext()) {
    5. System.out.println(iterator.next());
    6. }
    7. }