JavaEE之泛型

1 阅读9分钟

认识泛型

泛型介绍

  • 定义类、接口、方法时,同时声明了一个或者多个类型变量(如:),称为泛型类、泛型接口,泛型方法、它们统称为泛型
    public class ArrayList<E>{
        ...
    }
    
  • 作用:泛型提供了在编译阶段约束所能操作的数据类型,并自动进行检查的能力!(先确定类型,再创建对象)
  • 这样可以避免强制类型转换,及其可能出现的异常。
  • 泛型的本质:把具体的数据类型作为参数传给类型变量。

剖析ArrayList是如何使用泛型进行类型校验的

  • 可以看到如果不指定ArrayList中的泛型参数的类型,可以向ArrayList对象中存入任何类型的值,但当我们指定了ArrayList的参数后,ArrayList对象就只能存入泛型参数指定的类型的值了,如下方代码所示:
import java.util.ArrayList;
public class Demo041 {

    public static void main(String[] args) {
        // 目标:使用ArrayList存储数据,感受泛型在编译阶段约束类型,不用强制类型转换
 //1.不使用泛型操作数据(每个元素类型是Object)
ArrayList list1 = new ArrayList();
        list1.add("hello");
        list1.add(100);
        list1.add(3.14);
        //获取第一个元素
Object one = list1.get(0);
        //得到真实类型
String oneStr = (String) one;
        //输出第一个元素的字符的个数
System.out.println("list1第一个元素字符串的字符个数:"+oneStr.length());

        //2.使用泛型操作数据
ArrayList<String> list2 = new ArrayList<>();
        list2.add("hello");
        // list2.add(100); 报错,在编译阶段会约束类型必须为字符串
 // list2.add(3.14); 报错,在编译阶段会约束类型必须为字符串
 //获取第一个元素
String a = list2.get(0);
        //输出第一个元素的字符的个数
System.out.println("list2第一个元素字符串的字符个数:"+a.length());
    }
}

image.png

  • 可以按住ctrl+左键进入ArrayList类的源码 image.png

  • 提供了三个构造方法,其中有一个构造方法有一个向上通配泛型就需要链式传入创建ArrayList时指定的泛型参数 image.png

    • ArrayList(int)接收一个int参数,用来创建一个有初始容量的容器,可以一次分配足够多的空间避免扩容带来的性能损失
    • ArrayList()用来创建一个空容器
    • ArrayList(Collection<?extendsE>),这个构造方法是一个容器转换的方法,Collectrion作为容器的父类,可以持有任意子类的容器对象,同时里面用到了向上通配泛型,该泛型不会检查指定类型的子类类型,再保证最大兼容的同时避免了精度丢失。这个向上统配泛型就是我们创建ArrayList对象时指定的泛型参数,该构造方法运行后就会将方法内的其他容器转换为ArrayList容器
  • 可以看到类中的成员方法的原型都有使用泛型参数的,这样既可以不用因为不同参数重复造轮子,又可以利用创建ArrayList对象时指定的泛型参数,做类型校验

    image.png

    image.png

自定义泛型类

语法:

修饰符 class 类名<类型变量,类型变量,…> { 

}


public class ArrayList<E>{
    . . .
}
  • 注意:类型变量建议用大写的英文字母,常用的有:E、T、K、V
    • E - Element(元素)最常见于集合类,表示集合中存储的元素类型
    • T - Type(类型)最通用的类型参数,表示任意类型
    • K - Key(键) ​ 和 V - Value(值)用于映射关系,成对出现,用于表示键和值的类型

应用实例

  • 在后面学的设计模式中,有一个种设计模式叫做装饰器模式,即为在不改变原有接口及其实现的情况下增强功能,如下方代码所示,可以给ArrayList设计一个类增强功能:
    • 这里就是利用了自定义泛型类,创建类的对象时先指定类型,指定的类型,利用泛型参数,链式指定了类中的成员变量和成员方法的泛型
    import java.util.ArrayList;
    public class HeiMaArrayList<E>{
    
        //定义数组集合
    private ArrayList<E> arrayList = new ArrayList<>();
    
        public void add(E e){
            System.out.println("开始自己的集合数组添加元素。。。");
            arrayList.add(e);
        }
    
        public E get(int index){
            System.out.println("开始自己的集合数组查询元素。。。");
            return arrayList.get(index);
        }
    
        public void remove(E e){
            arrayList.remove(e);
        }
    
        @Override
        public String toString() {
            arrayList.forEach(e-> System.out.println(e));
            return "";
        }
    }
    public class Demo051 {
    
        public static void main(String[] args) {
            //目标:使用自己的泛型类集合数组操作数据
    
        HeiMaArrayList<String> list = new HeiMaArrayList<>();
            list.add("hello");
            list.add("world");
            System.out.println("list添加元素后:");
            System.out.println(list);
            String s = list.get(0);
            System.out.println("第一个元素:"+s);
            list.remove("hello");
            System.out.println("list移除第一个元素后:");
            System.out.println(list);
        }
    }
    

自定义泛型接口

语法

修饰符 interface 接口名<类型变量,类型变量,…> { 

}

public interface A<E>{
    . . .
}

应用实战

  • 需求
    • 现在有两个分别用于存放学生类实例化对象和教师类实例化对象的容器,需要实现一个工具类用于操作两个容器
  • 分析:
    • 单独实现两个工具类(不推荐):面对复杂业务开发场景需要在代码中写明白遇到那个对象就调用与之对应工具类的实例类中的方法,会导致代码出现大量的if-elseif-else
    • 设计一个工具类接口用泛型做类型检验,分别为不同类型实现接口(推荐):大大减少了复杂开发场景的代码量,操作容器对象只需要调用接口并传入对应容器内元素的类作为泛型参数
  • 代码实现:
    • DataOperator操作容器的工具接口
    public interface DataOperator<T> {
    
        void add(T t);
    
        void update(T t);
    
        void delete(T t);
    
        T get(int index);
    }
    
    • Student学生类
    public class Student {
    
        private int stuNo; //学号
    private String StuName; //学生姓名
    
    public Student() {
        }
    
        public Student(String stuName, int stuNo) {
            StuName = stuName;
            this.stuNo = stuNo;
        }
    
        public String getStuName() {
            return StuName;
        }
    
        public void setStuName(String stuName) {
            StuName = stuName;
        }
    
        public int getStuNo() {
            return stuNo;
        }
    
        public void setStuNo(int stuNo) {
            this.stuNo = stuNo;
        }
    }
    
    • Teacher教师类
    public class Teacher {
    
        private String name;
        private String hobby;
    
        public Teacher(){}
        public Teacher(String name, String hobby) {
            this.name = name;
            this.hobby = hobby;
        }
    
        public String getHobby() {
            return hobby;
        }
    
        public void setHobby(String hobby) {
            this.hobby = hobby;
        }
    
        public String getName() {
            return name;
        }
    
        public void setName(String name) {
            this.name = name;
        }
    }
    
    • StudentDataOperator操作学生容器的工具实现类
    public class StudentDataOperator implements DataOperator<Student>{
        @Override
        public void add(Student student) {
            System.out.println("添加学生信息");
        }
    
        @Override
        public void update(Student student) {
            System.out.println("修改学生信息");
        }
    
        @Override
        public void delete(Student student) {
            System.out.println("删除学生信息");
        }
    
        @Override
        public Student get(int index) {
            System.out.println("查询学生信息");
            return null;
        }
    }
    
    • TeacherDataOperator操作教师容器的实现类
    public class TeacherDataOperator implements DataOperator<Teacher> {
        @Override
        public void add(Teacher teacher) {
            System.out.println("添加老师信息");
        }
    
        @Override
        public void update(Teacher teacher) {
            System.out.println("修改老师信息");
        }
    
        @Override
        public void delete(Teacher teacher) {
            System.out.println("删除老师信息");
        }
    
        @Override
        public Teacher get(int index) {
            System.out.println("查询老师信息");
            return null;
        }
    }
    
    • Demo061主类
    public class Demo061 {
    
        public static void main(String[] args) {
            //创建老师对象
    Teacher teacher = new Teacher();
            DataOperator<Teacher> dataOperator = new TeacherDataOperator();
            dataOperator.add(teacher);
            dataOperator.get(1);
            dataOperator.update(teacher);
            dataOperator.delete(teacher);
    
            //创建学生对象
            // Student student = new Student();
            // DataOperator<Student> dataOperator = new StudentDataOperator();
            // studentDataOperator.add(student);
            // studentDataOperator.update(student);
            // studentDataOperator.delete(student);
            // studentDataOperator.get(0);
        }
    }
    
    • 除了直接调用接口,也可以结合多态和构造函数达到强制类型检查的目的
    package DataOperator;
    
    public class SchoolBox<E> {
        E data;
        private DataOperator<E> dataOperator;
    
        public SchoolBox(DataOperator<E> dataOperator) {
            this.dataOperator = dataOperator;
        }
    
        public DataOperator<E> getDataOperator() {
            return dataOperator;
        }
    
    }
    
    package DataOperator;
    public class Demo061 {
    
        public static void main(String[] args) {
            SchoolBox<Teacher> eSchoolBox = new SchoolBox<Teacher>(new TeacherDataOperator());
            eSchoolBox.getDataOperator().add(new Teacher());
            eSchoolBox.getDataOperator().update(new Teacher());
            eSchoolBox.getDataOperator().delete(new Teacher());
    
        }
    }
    
    如果传入的对象和先确定的类型检查不匹配则会报错 image.png

泛型方法

语法

修饰符 <类型变量,类型变量,…>  返回值类型 方法名(形参列表) {

}

非泛型类中使用泛型方法方式

public static <T> void test(T t){
    
}

泛型类中使用泛型方法

public E get(int index){
    return arrayList.get(index);
}

例子

package com.itheima._07静态泛型方法与泛型通配符上下限;

/**
* @Description Demo071
* @Author songyu
* @Date 2025-09-30  14:46
*/
public class Demo071 {

    public static void main(String[] args) {

        Student[] students = new Student[10];
        Teacher[] teachers = new Teacher[2];
        printArrayLength(students);
        printArrayLength(teachers);
    }

    // 泛型类中使用泛型方法语法 :  static <T> 返回值  方法名(T 变量名){}
public static <T> void printArrayLength(T[] array){
        System.out.println("数组元素的个数:"+array.length);
    }
}

class Student{}
class Teacher{}

泛型通配符

引入

  • 在前面的ArrayList源码剖析中,可以看到有一个构造方法ArrayList(Collection<?extendsE>),这个构造方法是一个容器转换的方法,Collectrion作为容器的父类,可以持有任意子类的容器对象,同时里面用到了向上通配泛型,该泛型不会检查指定类型的子类类型,再保证最大兼容的同时避免了精度丢失。这个向上统配泛型就是我们创建ArrayList对象时指定的泛型参数,该构造方法运行后就会将方法内的其他容器转换为ArrayList容器,这就是我们对泛型通配符的初步了解

什么是通配符

  • 通配符:就是 “?” ,可以在“使用泛型”的时候代表一切类型; E T K V 是在定义泛型的时候使用。
  • 泛型的上下限
    • 泛型上限: ? extendsCar: ?能接收的必须是Car或者其子类 。
    • 泛型下限: ? superCar : ? 能接收的必须是Car或者其父类。

实例代码

class Che{}
class Car extends Che{}
class BMW extends Car{}
class Cat{}
import java.util.List;
public class Demo072 {

    public static void main(String[] args) {
        //目标:泛型通配符,上限和下限
         // List<?> :?可以代表任意类型,和List<Object>一样
         // List<Car>:  写死类型,只能是Car, 父类或子类都不可以, 使用最多
         // List<? extends Car> :泛型的上限,?可以代表Car和Car的子类
         // List<? super Car> :泛型的下限,?可以代表Car和Car的父类
         // 注意 不带泛型的List都可以被上面接收,但是不建议不使用泛型集合

         //分别创建不同类型的List对象
        List<Che> cheList = List.of(new Che());
        List<Car> carList = List.of(new Car());
        List<BMW> bmwList = List.of(new BMW());
        List<Cat> catList = List.of(new Cat());

        //打印上面4个集合
        printCar1(cheList);
        printCar1(carList);
        printCar1(bmwList);
        printCar1(catList);

        // printCar2(cheList); 报错,因为不是car
        printCar2(carList);
        // printCar2(bmwList);报错,因为不是car
         // printCar2(catList);报错,因为不是car

         // printCar3(cheList); 报错,因为不是Car及其子类
        printCar3(carList);
        printCar3(bmwList); //正确,因为是Car的子类
         // printCar3(catList);报错,因为不是Car及其子类

        printCar4(cheList);
        printCar4(carList);
        // printCar4(bmwList);报错,因为不是Car及其父类
      // printCar4(catList);报错,因为不是Car及其父类

}

    public static void printCar1(List<?> list){
        System.out.println(list);
    }
    public static void printCar2(List<Car> list){
        System.out.println(list);
    }
    public static void printCar3(List<? extends Car> list){
        System.out.println(list);
    }
    public static void printCar4(List<? super Car> list){
        System.out.println(list);
    }

}

泛型支持的类型

  • 泛型不支持基本数据类型,只能支持对象类型(引用数据类型)。 包装类

    包装类就是把基本类型的数据包装成对象的类型。

    基本数据类型对应的包装类(引用数据类型)
    byteByte
    shortShort
    intInteger
    longLong
    charCharacter
    floatFloat
    doubleDouble
    booleanBoolean
  • 关于包装数据类型的详解,请查看JavaEE之包装类型包装类 - 掘金