基本概念

通常集合中可以存放不同类型的对象,是因为将所有对象都看做Object类型放入的,因此从集合中取出元素时也是Object类型,为了表达该元素真实的数据类型,则需要强制类型转换,而强制类型转换可能会引发类型转换异常。

为了避免上述错误的发生,从Java5开始增加泛型机制,也就是在集合名称的右侧使用<数据类型>的方式来明确要求该集合中可以存放的元素类型,若放入其它类型的元素则编译报错。

  1. import java.util.ArrayList;
  2. import java.util.List;
  3. public class GenericDemo {
  4. public static void main(String[] args) {
  5. //1.准备支持泛型机制的list集合 明确集合中元素是String乐境
  6. List<String> strlist = new ArrayList<String>();
  7. //2.集合中添加元素打印
  8. strlist.add("hello");
  9. strlist.add("list");
  10. //3.集合中添加其它类型元素编译标红
  11. // strlist.add(1);
  12. //打印
  13. System.out.println(strlist);//[hello, list]
  14. //获取元素
  15. String s = strlist.get(1);
  16. System.out.println("获取元素为:" + s);//获取元素为:list
  17. }
  18. }
  • 泛型只在编译时期有效,在运行时期不区分是什么类型。

菱形特性

  • java7后开始的新特性
  • 对应的等号后面<>声明类型可以省略「反正写也是和前面保持一致」
    • 前后<>不能省略
    • 前面<>内类型不能省略
    • 后面<>内类型能省略

底层原理

泛型的本质就是参数化类型,也就是让数据类型作为参数传递,其中E相当于形式参数负责占位,

而使用集合时<>中的数据类型相当于实际参数,用于给形式参数E进行初始化,让集合中所有的E被实际参数替换,

由于实际参数可以传递各种各样广泛的数据类型,因此为泛型

可以看到对应的List及其实现类还有Collection都有<E>,对应的方法里面参数和返回值也是<E>, 这个E相当于形式参数负责占位

  1. public static void show(int i) {
  2. ...
  3. }
  4. show(10);
  • 实际参数为10
    • 负责给形式参数初始化
  • i为形式参数,负责占位

show(10)相当于调用show方法的时候把10传给了i;后面方法里用到i的时候对应的值就是10

int i = 10

  • String 叫做实际参数
  • E 叫做形式参数,负责占位
    • 一旦被String替换后,后面的E都替换成了String类型
  1. E = String;
  2. public interface List {
  3. ...
  4. }
  5. List<String> lt1 = ...;

这里只是说了String类型,也可以传入Integer、Person等类型,这就是我们说的泛型

自定义泛型接口

泛型接口和普通接口的区别就是后面添加了类型参数列表,可以有多个类型参数,如:等。

自定义泛型类

泛型类和普通类的区别就是类名后面添加了类型参数列表,可以有多个类型参数,如:等。

实例化泛型类时应该指定具体的数据类型,并且是引用数据类型而不是基本数据类型。

  1. /**泛型实体类
  2. * 自定义泛型类,T相当于形式参数负责占位,具体数值由实参决定
  3. * T 看做是一种名字为T的数据类型即可
  4. */
  5. public class Animal<T> {
  6. private String name;
  7. private int age;
  8. private T sex;
  9. //get,set toString 构造方法
  10. public T getSex() {
  11. return sex;
  12. }
  13. public void setSex(T sex) {
  14. this.sex = sex;
  15. }
  16. }

对应声明的时候,可以直接声明,就像Collection集合和List集合声明的时候一样。
对应的声明完成后,里面可以放的类型是Object,同样Animal如果没有<>的声明,
对应的sex类型就是Object

  1. //list集合声明的时候可以带参
  2. Animal animal = new Animal("小黑",1,"女生");
  3. System.out.println(animal);//Animal{name='小黑', age=1, sex=女生}
  • 创建对象的同时指定数据类型,这样用于给T进行初始化
  1. Animal<Integer> animal1 = new Animal();
  2. animal1.setSex(1);
  3. System.out.println(animal1);//Animal{name='null', age=0, sex=1}

linkList一样使用,指定的时候就是具体的类型,不指定就是Object类型

父子类泛型

  • 父类有泛型,子类可以选择保留泛型也可以选择指定泛型类型。
  • 子类必须是“富二代”,子类除了指定或保留父类的泛型,还可以增加自己的泛型。

示例

不保留泛型并且没有指定类型
  • 丢弃泛型

SubAnimal继承的时候没有把Animal的泛型保留下来,Animal里面的T就默认为是Object类型

  • 没有指定类型时就会默认解析为Object
  1. //此时Animal类的泛型默认为Object类型 也叫擦除
  2. public class SubAnimal extends Animal{
  3. }
  1. //声明SubAnimal类型的引用指向SubAnimal并调用set方法进行测试
  2. //SubAnimal<String> sa = new SubAnimal();//SubAnimal不支持泛型
  3. SubAnimal sa = new SubAnimal();
  4. //sa.setSex();//Object类型
  5. sa.setSex("女生");

泛型 - 图1

子类虽然继承父类,但是对应泛型没有继承过来,声明的时候不能<>指定对应泛型类型。
直接声明,对应的类,泛型T默认为Object。可以setSex看到对应参数提示为Object

不保留泛型但是指定了泛型类型
  1. //这个时候Animal类的T被指定为String类型
  2. public class SubAnimal extends Animal<String> {
  3. }
  1. //声明SubAnimal类型的引用指向SubAnimal并调用set方法进行测试
  2. //SubAnimal<String> sa = new SubAnimal();//SubAnimal不支持泛型
  3. SubAnimal sa = new SubAnimal();
  4. //sa.setSex();//String类型
  5. sa.setSex("女生");

直接声明,对应的类,泛型T默认为String类型。可以setSex看到对应参数提示为String

保留父类的泛型T
  1. public class SubAnimal<T> extends Animal<T> {
  2. }
  1. SubAnimal<String> sa = new SubAnimal();
  2. sa.setSex("女");//String

增加自己的泛型
  1. public class SubAnimal<T, E> extends Animal<T> {
  2. }
  1. SubAnimal<Boolean,Integer> sa = new SubAnimal<>();
  2. sa.setSex(true);

泛型 - 图2

自定义泛型方法

泛型方法就是我们输入参数的时候,输入的是泛型参数,而不是具体的参数。
我们在调用这个泛型方法的时需要对泛型参数进行实例化。

泛型方法的格式

  1. [访问权限] <泛型> 返回值类型 方法名([泛型标识 参数名称]) { 方法体; }

在静态方法中使用泛型参数的时候,需要我们把静态方法定义为泛型方法。

  1. public static <T1> void show(T1[] a){
  2. for (T1 t:a){
  3. System.out.println("t:"+t);
  4. }
  5. }
  • 参数类型是T1,自定义的类型。需要在方法返回值前面声明对应自定义的类型。

T1不是官方支持的类型,也不是我们自定义的类型。对应的不认识T1。这是一个泛型类型不是具体参数类型,所以要在返回值前面加上一个T1。这里就是表示是一个泛型。但是要注意,方法的T1和类上面的T是两种不同的概念

  • 调用泛型方法
  1. Integer[] arr = {11,22,44,55,99};
  2. Animal.show(arr);
  1. //这不是泛型方法
  2. public T getSex() {
  3. return sex;
  4. }
  • 在对象声明以后才能调用,泛型方法static修饰的就不需要实例化对象
  • 不是泛型方法,该方法不能用static修饰,因为T需要在new对象时才能明确类型

泛型在继承上的体现

如果B是A的一个子类或子接口,而G是具有泛型声明的类或接口,则G<B>并不是G<A>的子类型!

比如:StringObject的子类,但是List<String>并不是List<Object>的子类。

  • 创建一个父类
  1. public class Person {
  2. }
  • 创建一个子类继承父类
  1. public class Man extends Person{
  2. }
  • G<B>并不是G<A>的子类型
  1. public class GenericDemo {
  2. public static void main(String[] args) {
  3. List<Person> lp = new ArrayList<>();
  4. List<Man> lm = new ArrayList<>();
  5. // lp = lm;
  6. }
  7. }

泛型 - 图3

他们之间不具备公共关系,Person、Man对应的是否有公共的父类,为了找到对应的父类,就会有了一个公共的通配符。通配符有以下3种形式:

通配符的使用

有时候我们希望传入的类型在一个指定的范围内,此时就可以使用泛型通配符了。

如:之前传入的类型要求为Integer类型,但是后来业务需要Integer的父类Number类也可以传入。

泛型中有三种通配符形式

<?>
  • 无限制通配符
  • 表示我们可以传入任意类型的参数。

?类似我们导包的时候的*,代表的是任意的。就是无限制的;指定任意的类型,不是任意类型的对象

  • 不支持添加,支持获取
  1. public class GenericDemo {
  2. public static void main(String[] args) {
  3. List<Person> lp = new ArrayList<>();
  4. List<Man> lm = new ArrayList<>();
  5. lm.add(new Man());
  6. //试图将lm的数值赋值给lp,也就是发生List<Man>类型向List<Person>类型的转换
  7. // lp = lm;Error: 类型之间不具备父子类关系
  8. // 使用通配符作为泛型类型的公共父类
  9. List<?> lt3 = new LinkedList<>();
  10. lt3 = lp; // 可以发生List<Person>类型到List<?>类型的转换
  11. lt3 = lm; // 可以发生List<Man>类型到List<?>类型的转换
  12. System.out.println(lt3);
  13. Object o = lt3.get(0); // ok,支持元素的获取操作,全部当做Object类型来处理
  14. System.out.println("o:"+o);//打印man对象地址
  15. }
  16. }

<? extends E>
  • 表示类型的上界是E,只能是E或者是E的子类。
  • 不支持添加
  1. //使用有限制的通配符
  2. List<? extends Person> lp1 = new ArrayList<>();
  3. //不支持元素添加
  4. //lp1.add(new Person());
  5. //lp1.add(new Man());
  6. //lp1.add(new Object());

泛型 - 图4

  • 支持获取,获取到的是父类及其子类 类型
  1. //可以获取元素 获取到的是Person 及其子类
  2. Person person = lp1.get(0);

<? super E>
  • 表示类型的下界是E,只能是E或者是E的父类。
  • 支持添加,添加的是Animal及其父类,但是有可能只是Animal所以添加Object失败
  • 可以获取,获取到的就是ObjecObject类型
  1. List<? super Person> lp2 = new ArrayList<>();
  2. lp2.add(new Person());
  3. lp2.add(new Man());
  4. //lp2.add(new Object());//超过Animal类型
  5. Object object = lp2.get(0);