【Java加强】2 泛型 | 打卡day1

- 第 158 篇 -
Date: 2026 - 02- 02
Author: 郑龙浩(仟墨)

懒散了一阵子,好久没学Java了,今天将之前学的内容重新复习了一遍,然后还有两篇1月份的笔记没有发布,现在发一下其中第二篇

文章目录

  • 【Java加强】泛型
    • [1 基本介绍](#1 基本介绍)
      • [1.1 泛型是什么?](#1.1 泛型是什么?)
      • [1.2 举例解释](#1.2 举例解释)
    • [2 泛型类](#2 泛型类)
      • [2.1 泛型类语法格式?](#2.1 泛型类语法格式?)
      • [2.2 类型变量(泛型信息) 的 命名规则 | 别名](#2.2 类型变量(泛型信息) 的 命名规则 | 别名)
      • [2.3 什么叫做「泛型信息」?](#2.3 什么叫做「泛型信息」?)
        • [2.4 泛型类的实例](#2.4 泛型类的实例)
    • [3 泛型接口](#3 泛型接口)
    • [4 泛型方法、通配符(使用泛型) 、上下限](#4 泛型方法、通配符(使用泛型) 、上下限)
      • [4.1 泛型方法](#4.1 泛型方法)
      • [4.2 通配符 `?` 与使用泛型](#4.2 通配符 ? 与使用泛型)
      • [4.3 使用泛型 和 定义泛型 的区别 | 通配符(?) 和 类型参数(ETKV)的区别](#4.3 使用泛型 和 定义泛型 的区别 | 通配符(?) 和 类型参数(ETKV)的区别)
        • [4.3.1 声明位置不同,? 不能在方法内部使用](#4.3.1 声明位置不同,? 不能在方法内部使用)
        • [4.3.2 可操作性不同](#4.3.2 可操作性不同)
        • [4.3.3 类型检查不同](#4.3.3 类型检查不同)
        • [4.3.4 实际应用对比](#4.3.4 实际应用对比)
      • [4.4 什么时候使用通配符`?` (使用泛型)?](#4.4 什么时候使用通配符? (使用泛型)?)
      • [4.5 什么时候使用类型参数`ETKV`(定义泛型)](#4.5 什么时候使用类型参数ETKV(定义泛型))
      • [4.6 建议](#4.6 建议)
      • [4.7 泛型的上下限](#4.7 泛型的上下限)
        • [4.7.1 假设的类继承关系](#4.7.1 假设的类继承关系)
        • [4.7.2 泛型上限 `<? extends Car>`](#4.7.2 泛型上限 <? extends Car>)
        • [4.7.3 泛型下限 `<? super Car>`](#4.7.3 泛型下限 <? super Car>)

【Java加强】泛型

本笔记创作时间:2026-01-07

1 基本介绍

1.1 泛型是什么?

简单说:让类型也变成参数,在创建对象时才确定具体类型

泛型是一种编程语言特性,允许在定义类、接口、方法时使用类型参数。在创建对象或调用方法时,可以指定具体的类型。

1.2 举例解释

java 复制代码
package zhenglonghao.generic1;

import java.util.ArrayList;

// Date: 2026-01-07 Author: 郑龙浩
// 泛型演示
public class Generic1 {
    public static void main(String[] args) {

        // 1 不使用泛型 - 需要类型转换,可能引发运行时错误
        ArrayList list1 = new ArrayList(); // 元素类型是 Object,object可以转为其他的类型
        list1.add("Hello");
        list1.add(123);  // 可以存储不同类型,但...
        Object obj = list1.get(0); // List集合取出元素,默认是Object类型

        String str1 = (String) obj;  // 需要强转,将object转为String

        // 因为第二个元素不能从object转为String,所以报错,应该是object转为int
        // String str2 = (String) list1.get(1);  // 运行时错误:ClassCastException


        //============================================================================

        // 2 使用泛型 - 类型安全,编译时检查
        ArrayList<String> list2 = new ArrayList<>(); // 元素类型是 String,不是 Object 了
        list2.add("Hello");
        // list2.add(123);  // 编译错误:只能添加String类型
        String str3 = list2.get(0);  // 自动类型推断,无需强转
    }
}

扩展 : JDK7开始ArrayList<String> list2 = new ArrayList<String>()中的String可以去掉,如:ArrayList<String> list2 = new ArrayList<>()

2 泛型类

架构师必备的技能

2.1 泛型类语法格式?

java 复制代码
修饰符 class 类名<类型变量> {
    // 类体
}
java 复制代码
// 定义泛型类
public class MyArrayList<E> {
    
}

// 使用泛型类
ArrayList<String> list1 = new MyArrayList<>();
ArrayList<Integer> list2 = new MyArrayList<>();

2.2 类型变量(泛型信息) 的 命名规则 | 别名

  1. 必须使用大写的英文字母

  2. 常用单字母表示

  • E:Element(元素),集合中常用
  • T:Type(类型),通用类型
  • K:Key(键),映射中的键
  • V:Value(值),映射中的值
  1. 可声明多个类型变量,用逗号分隔:
java 复制代码
public class HashMap<K, V> { 
    ... 
}

2.3 什么叫做「泛型信息」?

定义泛型类时,需要在类名后的尖括号<>中声明"类型变量",如右例中的<E>。这里的字母E (代表Element)就是最常用的"类型变量"之一。在泛型语境下,这个在编写类时声明、用于占位 ,并在使用类时被具体类型(如String)替换E,其本质就是泛型信息 。它是一段在编译阶段指导编译器进行类型检查和转换的元信息。因此,E作为类型变量的具体符号,就是泛型信息在代码中的直接体现

简单点说:E 之类的占位符,就是指的泛型信息

2.4 泛型类的实例

MyArrayList.java 文件 - 自定义泛型类

java 复制代码
package zhenglonghao.generic1;
import java.util.Arrays;
// Date: 2026-01-07 Author: 郑龙浩 AI 生成代码

// 定义泛型类 MyArrayList<E>
// E 是类型参数,创建对象时指定具体类型(如 String、Integer 等)
// 泛型类可以让同一个类支持多种数据类型,且保证类型安全
public class MyArrayList<E> {
    // 使用Object数组存储元素,因为Java泛型是编译时检查,运行时类型会被擦除
    // 实际上,泛型信息在运行时不可用,所以用Object数组存储
    private Object[] elements;

    // 记录当前列表中元素的个数
    private int size = 0;

    // 默认初始容量
    private static final int DEFAULT_CAPACITY = 10;

    // 无参构造方法,创建默认容量的数组
    public MyArrayList() {
        // 注意:不能直接创建泛型数组:new E[DEFAULT_CAPACITY] 是不允许的
        elements = new Object[DEFAULT_CAPACITY];
    }

    // 添加元素到列表末尾
    // 参数类型是E,确保只能添加指定类型的元素
    public void add(E element) {
        ensureCapacity();  // 检查容量是否足够
        elements[size++] = element;  // 在末尾添加元素,size自增
    }

    // 根据索引获取元素
    // 返回值类型是E,取出元素时会自动进行类型转换
    public E get(int index) {
        if (index < 0 || index >= size) {
            throw new IndexOutOfBoundsException("索引越界: " + index);
        }
        // 由于elements是Object数组,需要强制转换为E类型
        // 但这是类型安全的,因为添加时只允许添加E类型的元素
        return (E) elements[index];
    }

    // 返回当前列表中元素的数量
    public int size() {
        return size;
    }

    // 检查并确保数组容量足够
    // 如果当前数组已满,将数组容量扩展为原来的两倍
    private void ensureCapacity() {
        if (size >= elements.length) {
            elements = Arrays.copyOf(elements, elements.length * 2);
        }
    }

    // 根据索引删除元素
    // 返回被删除的元素
    public E remove(int index) {
        if (index < 0 || index >= size) {
            throw new IndexOutOfBoundsException("索引越界: " + index);
        }

        // 获取要删除的元素
        E removed = (E) elements[index];

        // 将删除位置后面的元素都向前移动一位
        for (int i = index; i < size - 1; i++) {
            elements[i] = elements[i + 1];
        }

        // 将最后一个位置设为null,帮助垃圾回收
        elements[--size] = null;
        return removed;
    }

    // 判断列表是否为空
    public boolean isEmpty() {
        return size == 0;
    }

    // 清空列表
    // 将所有元素设为null,帮助垃圾回收
    public void clear() {
        for (int i = 0; i < size; i++) {
            elements[i] = null;
        }
        size = 0;  // 重置大小为0
    }

    // 测试代码
    public static void main(String[] args) {
        // 创建存储String的MyArrayList
        // 此时,类中所有的E都会被替换为String
        MyArrayList<String> stringList = new MyArrayList<>();
        stringList.add("Hello");
        stringList.add("World");

        // 创建存储Integer的MyArrayList
        // 此时,类中所有的E都会被替换为Integer
        MyArrayList<Integer> intList = new MyArrayList<>();
        intList.add(100);
        intList.add(200);

        // 类型安全:编译时会检查类型
        // stringList.add(123);  // 编译错误:只能添加String
        // intList.add("abc");   // 编译错误:只能添加Integer
    }
}

Test.java - 执行

java 复制代码
package zhenglonghao.generic1;

public class Test1 {
    public static void main(String[] args) {
        // 创建存储String的列表
        MyArrayList<String> list1 = new MyArrayList<>();
        list1.add("Hello");
        list1.add("World");

        String first = list1.get(0);  // 类型安全,无需强制转换
        System.out.println(first);    // 输出: Hello

        // 创建存储Integer的列表
        MyArrayList<Integer> list2 = new MyArrayList<>();
        list2.add(100);
        list2.add(200);

        Integer num = list2.get(1);   // 类型安全,无需强制转换
        System.out.println(num);      // 输出: 200

        // 类型安全检查
        // list1.add(123);  // 编译错误:只能添加String类型
        // list2.add("abc");  // 编译错误:只能添加Integer类型
    }
}
/* 执行结果
Hello
200
 */

3 泛型接口

对学生、老师的数据进行操作,下面举例:

Student.java - 学生实体类(存储一个学生的数据)

java 复制代码
package zhenglonghao.generic2;

import lombok.Data;

@Data
public class Student {
    private String name;
    private int age;
}

Teacher.java - 老师实体类(存储一个老师的数据)

java 复制代码
package zhenglonghao.generic2;

import lombok.Data;

@Data
public class Teacher {
    private String name;
    private int age;
}

PeopleDAO.java - 泛型操作接口(或 People.java 接口)

StudentDAO 和 TeacherDAO 都是 PeopleDAO 的实现类

java 复制代码
package zhenglonghao.generic2;
// 自定义泛型接口
public interface PeopleDAO<T> {
    void add(T t);
    void delete (T t);
    void update (T t);
    // ...
}

StudentDAO.java - 学生操作实现

java 复制代码
package zhenglonghao.generic2;

import java.util.ArrayList;
import java.util.List;

public class StudentDAO implements People<Student> {
    private List<Student> studentList = new ArrayList<>();
    @Override
    public void add(Student student) {
        // ...
    }

    @Override
    public void delete(Student student) {
        // ...
    }

    @Override
    public void update(Student student) {
        // ...
    }
}

TeacherDAO.java - 老师操作实现

java 复制代码
package zhenglonghao.generic2;

import java.util.ArrayList;
import java.util.List;

public class TeacherDAO implements People<Teacher>{
    private List<Teacher> teacherList = new ArrayList<>();

    @Override
    public void add(Teacher teacher) {
        // ...
    }

    @Override
    public void delete(Teacher teacher) {
        // ...
    }

    @Override
    public void update(Teacher teacher) {
        // ...
    }
}

Test.java - 测试/主程序

java 复制代码
package zhenglonghao.generic2;

public class Test {

    public static void main(String[] args) {
        // 1 对学生数据进行操作
        StudentDAO studentDAO = new StudentDAO(); // 创建StudentDAO对象,用于操作学生数据
        Student student = new Student(); // 创建Student对象,用于保存学生数据
        student.setName("张三");
        student.setAge(18);
        studentDAO.add(student); // 将1个Student学生数据利用StudentDAO对象保存到数据库中


        // 2 对老师数据进行操作
        TeacherDAO teacherDAO = new TeacherDAO();
        Teacher teacher = new Teacher();
        teacher.setName("王五");
        teacher.setAge(30);
        teacherDAO.add(teacher);
        
    }
}

4 泛型方法、通配符(使用泛型) 、上下限

4.1 泛型方法

如下,getMax的作用是返回某数字的最大值,这个数字的类型可以是任意的,传参是String返回就是Sting,传参是Student返回就是Student

java 复制代码
修饰符 <类型变量,类型变量, ...> 返回值类型 方法名(形参列表) {
    
}

// ================
    public static <T> T getMax(T[] nums) {
    return ...
}

4.2 通配符 ? 与使用泛型

  • 通配符 ?表示"未知类型",用于在使用泛型时表示不关心具体类型。

① 通配符的三种形式

java 复制代码
// 1. 无界通配符
List<?> list;           // 可以是任何类型

// 2. 上界通配符
List<? extends Number> numbers;  // 只能是Number或其子类

// 3. 下界通配符  
List<? super Integer> integers;  // 只能是Integer或其父类

② 通配符的使用场景

java 复制代码
// 场景1:不关心元素类型,只读取
void printAll(Collection<?> coll) {
    for (Object obj : coll) {
        System.out.println(obj);
    }
}

// 场景2:作为方法参数,接受任何类型
int getSize(List<?> list) {
    return list.size();
}

③ 通配符的限制

java 复制代码
void process(List<?> list) {
    // 可以读取
    Object obj = list.get(0);
    
    // 不能写入(除了null)
    // list.add("hello");  // ❌ 编译错误
    list.add(null);       // ✅ 只能添加null
}

4.3 使用泛型 和 定义泛型 的区别 | 通配符(?) 和 类型参数(ETKV)的区别

4.3.1 声明位置不同,? 不能在方法内部使用
java 复制代码
// ? 只能在使用处
void method1(List<?> list) {  // 使用通配符
    // 不能在方法内部声明 ? 类型的变量
    // 也就是在方法内部,是不能使用?的
}

// T 可以在定义处
<T> void method2(List<T> list) {  // 定义类型参数
    T item = list.get(0);  // 可以在内部使用T
}
4.3.2 可操作性不同
操作 通配符 ? 类型参数 T
读取元素 只能作为 Object 可以作为 T
添加元素 只能添加 null 可以添加 T 类型元素
返回类型 不能作为返回类型 可以作为返回类型
类型变量声明 不能声明 ? 类型变量 可以声明 T 类型变量
4.3.3 类型检查不同
java 复制代码
// 用 ? 的类型检查较宽松
void addNumbers(List<?> list) {
    // 编译器:不知道list里是什么类型
    // 所以除了null,什么都不能添加
}

// 用 T 的类型检查严格
<T> void addItems(List<T> list, T item) {
    // 编译器:知道list里是T类型
    // 可以添加T类型元素
    list.add(item);
}
4.3.4 实际应用对比

在一定程度上,两者可以互换,但是复杂场景上,只能使用「类型参数」(定义泛型)

  1. 简单场景上 --> 「使用泛型」更好

    该方法只是为了取个长度,所以无需关注什么类型,用?就可以,如果用定义泛型,反而是脱裤子放屁,就没有必要了

    java 复制代码
    // 场景:打印列表大小
    // 用 ? 实现
    void printSize1(List<?> list) {
        System.out.println(list.size());  // 简单直接
    }
    
    // 用 T 实现
    <T> void printSize2(List<T> list) {
        System.out.println(list.size());  // 复杂,不必要
    }
  2. 复杂场景上 --> 只能「定义泛型」

    java 复制代码
    // 场景:交换列表元素
    // 用 ? 无法实现
    void swapWithWildcard(List<?> list, int i, int j) {
        Object temp = list.get(i);
        // list.set(i, list.get(j));  // ❌ 编译错误
        // list.set(j, temp);         // ❌ 编译错误
    }
    
    // 用 T 可以实现
    <T> void swapWithGeneric(List<T> list, int i, int j) {
        T temp = list.get(i);
        list.set(i, list.get(j));
        list.set(j, temp);
    }

4.4 什么时候使用通配符? (使用泛型)?

  1. 只需要读取数据,不关心具体类型
  2. 不需要修改集合的内容
  3. 方法逻辑和元素类型无关
  4. 作为方法参数,接收多种类型
java 复制代码
// 适合用 ? 的例子
boolean isEmpty(Collection<?> coll) {
    return coll.isEmpty();
}

void printElements(Iterable<?> iterable) {
    for (Object obj : iterable) {
        System.out.println(obj);
    }
}

4.5 什么时候使用类型参数ETKV(定义泛型)

  1. 需要向集合添加元素
  2. 需要返回特定类型
  3. 需要在方法内部操作类型
  4. 需要保持类型一致性
  5. 定义泛型类或泛型接口
java 复制代码
// 适合用 T 的例子
class Pair<K, V> {  // 定义泛型类
    private K key;
    private V value;
}

<T> List<T> merge(List<T> list1, List<T> list2) {
    List<T> result = new ArrayList<>(list1);
    result.addAll(list2);
    return result;
}

4.6 建议

优先使用通配符

  • 大多数情况下,通配符更简单
  • 代码更易读,维护性更好
  • 符合"最小知识原则"

需要时才用类型参数

  • 只有在需要操作类型时才用 <T>
  • 避免不必要的复杂性

4.7 泛型的上下限

4.7.1 假设的类继承关系

假设类的继承关系如下

Vehicle ← Car ← GasCar/ElectricCar

java 复制代码
// 爷爷类
class Vehicle { }  // 交通工具

// 爸爸类
class Car extends Vehicle { }  // 汽车

// 儿子类
class GasCar extends Car { }  // 汽油车
class ElectricCar extends Car { }  // 电动车
4.7.2 泛型上限 <? extends Car>

? 只能接收 Car 或 Car 的子类

java 复制代码
// 创建一个只读的"汽车或子类"列表
List<? extends Car> carList = new ArrayList<GasCar>();  // ✅ GasCar是Car的子类
List<? extends Car> carList2 = new ArrayList<ElectricCar>();  // ✅ ElectricCar是Car的子类
List<? extends Car> carList3 = new ArrayList<Car>();  // ✅ Car本身

// 错误例子
List<? extends Car> carList4 = new ArrayList<Vehicle>();  // ❌ Vehicle是Car的父类

注意:只能读,不能写

java 复制代码
// 可以读取
Car car = carList.get(0);  // ✅ 读取出来一定是Car或子类

// 不能写入
// carList.add(new GasCar());  // ❌ 编译错误
// carList.add(new Car());     // ❌ 编译错误
// carList.add(new ElectricCar());  // ❌ 编译错误

为什么不能写入?

因为编译器不知道carList具体是GasCarElectricCar还是Car,为了类型安全,禁止写入任何东西(null除外)。

4.7.3 泛型下限 <? super Car>

? 只能是 Car 或 Car 的父类

java 复制代码
// 创建一个可写的"Car或父类"列表
List<? super Car> carList = new ArrayList<Vehicle>();  // ✅ Vehicle是Car的父类
List<? super Car> carList2 = new ArrayList<Car>();  // ✅ Car本身
List<? super Car> carList3 = new ArrayList<Object>();  // ✅ Object是Car的祖先

// 错误例子
List<? super Car> carList4 = new ArrayList<GasCar>();  // ❌ GasCar是Car的子类

注意:可以写入Car及其子类,读取只能作为Object

java 复制代码
// 可以写入Car及其子类
carList.add(new Car());         // ✅
carList.add(new GasCar());      // ✅ GasCar是Car的子类
carList.add(new ElectricCar());  // ✅ ElectricCar是Car的子类

// 不能写入父类
// carList.add(new Vehicle());  // ❌ Vehicle是Car的父类

// 读取时只能作为Object
Object obj = carList.get(0);  // ✅
// Car car = carList.get(0);  // ❌ 编译错误

为什么读取只能作为Object?

因为carList可能是List<Vehicle>List<Object>,无法确定读取出来的具体类型,只能当作最通用的Object

为什么泛型上限不可以写入Car和Car子类,二泛型下限就可以写入Car和Car的子类?

  • 泛型上限 ? extends Car不能写入是因为编译器不知道具体是哪种Car的子类,为了保证类型安全必须禁止写入。 当你声明 List<? extends Car>时,编译器只知道这个列表里是Car或其子类,但不知道具体是GasCar、ElectricCar还是Car本身。如果允许写入Car对象,就可能把Car错误地放入一个专门存储GasCar的列表中,破坏类型安全。
  • 泛型下限 ? super Car可以写入Car及其子类是因为编译器知道这个容器至少能容纳Car类型。 无论实际是 List<Car>List<Vehicle>还是 List<Object>,Car及其子类的对象都能安全地存入,因为子类可以向上转型为父类,所以写入是安全的。简单说,上限不知道具体子类所以禁止写入,下限知道至少是父类所以允许写入子类。

说白了,泛型下限的时候,? 只能接收Car和Car的子类,那么也就意味着操作的一定是Car能容纳的,那么写入100%安全(不会接收父类)、

泛型上限可能接收父类,Car的父类Car无法容纳,所以不能写入

相关推荐
Hx_Ma162 小时前
SpringBoot注册格式化器
java·spring boot·后端
maplewen.2 小时前
C++11 std::function
开发语言·c++
阿里嘎多学长2 小时前
2026-02-02 GitHub 热点项目精选
开发语言·程序员·github·代码托管
V胡桃夹子2 小时前
VS Code / Lingma AI IDE Java 开发攻略手册
java·ide·人工智能
乔江seven2 小时前
【python轻量级Web框架 Flask 】1 Flask 初识
开发语言·后端·python·flask
独自破碎E2 小时前
【回溯】二叉树的所有路径
android·java
风景的人生2 小时前
application/x-www-form-urlencoded
java·mvc
sheji34162 小时前
【开题答辩全过程】以 基于Java的流浪猫救济中心系统的设计与实现为例,包含答辩的问题和答案
java·开发语言
毕设源码-郭学长2 小时前
【开题答辩全过程】以 高校选修课管理系统的设计与实现为例,包含答辩的问题和答案
java