- 第 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 类型变量(泛型信息) 的 命名规则 | 别名
-
必须使用大写的英文字母
-
常用单字母表示:
E:Element(元素),集合中常用T:Type(类型),通用类型K:Key(键),映射中的键V:Value(值),映射中的值
- 可声明多个类型变量,用逗号分隔:
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 实际应用对比
在一定程度上,两者可以互换,但是复杂场景上,只能使用「类型参数」(定义泛型)
-
简单场景上 --> 「使用泛型」更好
该方法只是为了取个长度,所以无需关注什么类型,用?就可以,如果用定义泛型,反而是脱裤子放屁,就没有必要了
java// 场景:打印列表大小 // 用 ? 实现 void printSize1(List<?> list) { System.out.println(list.size()); // 简单直接 } // 用 T 实现 <T> void printSize2(List<T> list) { System.out.println(list.size()); // 复杂,不必要 } -
复杂场景上 --> 只能「定义泛型」
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 什么时候使用通配符? (使用泛型)?
- 只需要读取数据,不关心具体类型
- 不需要修改集合的内容
- 方法逻辑和元素类型无关
- 作为方法参数,接收多种类型
java
// 适合用 ? 的例子
boolean isEmpty(Collection<?> coll) {
return coll.isEmpty();
}
void printElements(Iterable<?> iterable) {
for (Object obj : iterable) {
System.out.println(obj);
}
}
4.5 什么时候使用类型参数ETKV(定义泛型)
- 需要向集合添加元素
- 需要返回特定类型
- 需要在方法内部操作类型
- 需要保持类型一致性
- 定义泛型类或泛型接口
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具体是GasCar、ElectricCar还是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无法容纳,所以不能写入