一、泛型概述

1、什么是泛型

  • 泛型就是标签,加了泛型,就相当于加了标签,这个容器就只能放这一类物品。

  • 把元素的类型设参数,这个类型参数叫做泛型。Collection<E>,List<E>,ArrayList<E> 这个<E>就是类型参数,即泛型。

  • 所谓泛型,就是允许在定义类、接口时通过一个标识表示类中某个属性的类型或者是某个方法的返回值及参数类型。这个类型参数将在使用时(例如, 继承或实现这个接口,用这个类型声明变量 、创建对象时)确定(即传入实 际的类型参数,也称为类型实参)。

  • JDK1.5时引入。

2、为什么用泛型

  • 存储时:编译时进行类型检查来保证,存放的都是类型一样的数据,更安全。
  • 获取时:获取的都是类型一样的数据,无序强转

3、在集合中使用泛型

  • ① 集合接口或集合类在jdk5.0时都修改为带泛型的结构。
  • ② 在实例化集合类时,可以指明具体的泛型类型。

  • ③ 指明完以后,在集合类或接口中凡是定义类或接口时,内部结构(比如:方法、构造器、属性等)使用到类的泛型的位置,都指定为实例化的泛型类型。

    比如:add(E e) --->实例化以后:add(Integer e)。

  • ④ 注意点:泛型的类型必须是类,不能是基本数据类型。需要用到基本数据类型的位置,拿包装类替换。

  • ⑤ 如果实例化时,没有指明泛型的类型。默认类型为java.lang.Object类型。

  • ⑥在JDK7中新特性:类型推断

    • List<String> list = new List<>();后面的泛型可以省略

二、自定义泛型结构

1、泛型类、接口

  • 自定义泛型类代码实现:核心思想就是把T当做一个某某类型的参数,只不过需要在尖括号标明
/**
 * 自定义泛型类
 */
public class MyGeneric<T> {
    String name;
    int age;
    T decs;//可以把他当做一个类来看,但是并不是类,而是参数

    public MyGeneric() {
    }

    //带参构造器
    public MyGeneric(String name, int age, T decs) {
        this.name = name;
        this.age = age;
        this.decs = decs;
    }

    //get()方法
    public T getDecs() {
        return decs;
    }

    //set()方法
    public void setDecs(T decs) {
        this.decs = decs;
    }

    //toString()方法
    @Override
    public String toString() {
        return "MyGeneric{" +
                "name='" + name + '\'' +
                ", age=" + age +
                ", decs=" + decs +
                '}';
    }
}
  • 子类继承父类时,泛型的情况
    • 说明:子类是“富二代”,除了指定或保留父类的泛型,还可以增加自己的泛型。
    • 情况一:子类不保留父类的泛型
      • 父类没有类型,擦除,类型按照Object处理
      • 父类是具体类型
    • 情况二:子类保留父类的类型
      • 全部保留
      • 部分保留
    • 代码示例
class Father<T1, T2>{
}

//子类不保留父类的泛型
//1)没有类型,擦除--->这时,子类不是泛型
class Son1 extends Father{//等价于class Son extends Father<Object,Object>{
}

//2)具体类型--->这时,子类不是泛型
class son2 extends Father<Integer, String>{
}

//子类保留父类的泛型
//1)全部保留
class Son3<T1, T2> extends Father<T1, T2>{
}
//2)部分保留
class Son4<T2> extends Father<Integer, T2>{
}

//当然子类也可以加上自己的新的泛型类型

  • 注意点

    • 泛型类里的泛型是以参数的形式存在的。

    • 可以有多个参数:<T1,T2,T3>

    • 声明构造器时

      • 空参构造器和普通的一样 public MyGeneric() {}
      • 带参构造器体现在参数上 public MyGeneric(String name, int age, T decs) {
    • 泛型不同的引用不能相互赋值

      ArrayList<String> list1 = new ArrayList<>();
      ArrayList<Integer> list2 = new ArrayList<>();
      
      list1 = list2;//这样是错误的!
      
    • 泛型如果不指定,就会被擦除,泛型对应的类型按照Object处理,但不等价于Object。

    • 在类/接口上声明的泛型,在本类或本接口中即代表某种类型,可以作为非静态 属性的类型、非静态方法的参数类型、非静态方法的返回值类型。但在静态方法中不能使用类的泛型。(因为,静态方法随着类的加载而加载,而泛型是在实例化的时候指定,晚于静态方法出生)

    • 异常类不能声明为泛型的。

    • 如果想在构造器中初始化一个T类型的数组,要写作:

      T[] t = (T[])new Object[capacity]

2、泛型方法

  • 理解
    • 在方法中出现了泛型的结构,就是泛型方法。
    • 泛型方法中的泛型参数与类的泛型参数没有关系。也就是说:泛型方法所属的类是不是泛型无所谓。
    • 可以声明为静态的,因为泛型参数是在调用方法时确定的,而不是来加载时。
  • 代码实现一个泛型方法
public static <E> List<E> copyFromArrayToList(E[] arr){
    //这里的第一个<E>是为了不让编译器把E当做是一个我们自定义的类
    ArrayList<E> list = new ArrayList<>();

    for(E e : arr){
        list.add(e);
    }

    return list;
}

三、举例泛型类和泛型方法的使用场景

1、泛型类举例:

连接数据库时,操作数据库中的表的记录

  • DAO类:定义了数据库不同表的一些共性操作的类

    public class DAO<T> {}

  • CustomerDAO:定义了专门操作Customer表的类

    public class CustomerDAO extends DAO<Customer>{}

2、泛型方法举例

//泛型方法举例
    /*
    返回的东西是不确定的,比如
        需求1:表中有多少条记录
        需求2:获取工资最大值
     */
    public <E> E getSomething(){
        return null;
    }

四、泛型在继承上的体现

  • 首先,我们知道在多态下,子类的对象可以赋给父类的引用。
  • 但是,在泛型情况下,不再存在子父类的关系。例如,String是Object的子类,但是List<String >并不是List<Object> 的子类。同理在方法参数中也一样适用。这样就导致一个问题,同一功能的方法要重载很多次。
  • 区别,类B是类A的子类,那么B<T>仍然是A<T>的子类。

五、通配符

1、通配符

通配符用 ?来表示,含义为:类A是类B的父类,G<A> G<B>并不存在什么关系,二者的共同父类是:G<?>。这样就不必重载那么多功能相同的方法了。

2、添加通配符后数据的写入何人读出

  • 写入:对于List<?>,不能向其中添加数据,只能添加null;
  • 读出:可以读取,读取出的数据为Object类型

3、有限制条件的通配符

  • 首先,通配符?可以的范围可以理解为: (无穷小,无穷大)
  • ? extends A的范围就可以理解为:小于等于,即(无穷小,A]
  • ? super A的范围就可以理解为:大于等于,即[A,无穷大)
11-23 09:30