1. 为什么要有泛型
1.1 无泛型的痛点
在 JDK1.5 之前,集合容器类的元素类型被设计为Object,导致两个核心问题:
任何类型都可以添加到集合中,类型不安全;
读取出来的对象需要强转,繁琐,且可能有ClassCastException。
java
运行
import java.util.ArrayList;
import java.util.List;
public class ArrayListTest {
public static void main(String[] args) {
List list = new ArrayList();
// 需求:存放学生成绩(Integer类型)
list.add(78);
list.add(66);
list.add(19);
list.add(60);
// 问题1:类型不安全------可以添加任意类型(如String)
list.add("Tom");
for (Object score : list) {
// 问题2:必须强转,且可能抛出ClassCastException
Integer stuScore = (Integer) score;
System.out.print(stuScore);
int result = stuScore.compareTo(60);
if (result > 0) {
System.out.println("-优秀");
} else if (result == 0) {
System.out.println("-中等");
} else {
System.out.println("-差生");
}
}
}
}
运行上述代码会直接抛出ClassCastException,因为"Tom"无法转为Integer。
1.2 泛型的解决方案
JDK1.5 引入 "参数化类型",允许创建集合时指定元素类型,从根源解决问题:
import java.util.ArrayList;
import java.util.List;
public class ArrayListTest {
public static void main(String[] args) {
// 明确指定List只能存储Integer类型
List<Integer> list = new ArrayList();
list.add(78);
list.add(66);
list.add(19);
list.add(60);
// 编译报错:类型不匹配,无法添加String
// list.add("Tom");
// 无需强转,直接使用Integer类型
for (Integer stuScore : list) {
System.out.print(stuScore);
int result = stuScore.compareTo(60);
if (result > 0) {
System.out.println("-优秀");
} else if (result == 0) {
System.out.println("-中等");
} else {
System.out.println("-差生");
}
}
}
}
1.3 泛型的核心好处
- 类型安全:编译期校验元素类型,避免非法类型插入
- 简化代码:无需强制类型转换
- 代码复用:一套逻辑适配多种数据类型
2. 泛型类
2.1 定义格式
java
修饰符 class 类名<类型参数> { }
// 类型参数常用占位符:T(Type)、E(Element)、K(Key)、V(Value)
2.2 实战案例
java
class Generic<T> {
private T content;
public void setContent(T content) {
this.content = content;
}
public T getContent() {
return content;
}
}
public class GenericTest {
public static void main(String[] args) {
// 非泛型用法(不推荐):需强转,不安全
Generic generic = new Generic();
generic.setContent(19);
if ((Integer) generic.getContent() >= 18) { // 强制类型转换
System.out.println("已成年,可以...");
}
// 泛型用法(推荐):编译期校验,无需强转
Generic<Integer> generic2 = new Generic<>();
generic2.setContent(19);
// generic2.setContent("19"); // 编译报错:类型不匹配
if (generic2.getContent() >= 18) { // 直接使用Integer类型
System.out.println("已成年,可以...");
}
}
}
2.3 典型应用场景
泛型类的核心价值是一套逻辑处理多种类型数据。
Java 内置的ArrayList就是经典的泛型类:
java
// JDK源码核心逻辑(简化)
public class ArrayList<T> {
private T[] elements;
public boolean add(T e) {
// 添加元素逻辑...
}
public T get(int index) {
// 获取元素逻辑...
}
// 其他通用方法...
}
// 复用同一套逻辑处理不同类型
List<String> stringList = new ArrayList<>(); // 存储字符串
List<Integer> intList = new ArrayList<>(); // 存储整数
List<Person> personList = new ArrayList<>(); // 存储自定义对象
3. 泛型方法
3.1 定义格式
java
修饰符 <类型参数> 返回值类型 方法名(类型参数 变量名) { }
// 注意:泛型方法的类型参数需在返回值类型前声明
3.2 实战案例
java
class GenericUtil {
// 泛型方法:适配任意类型的打印逻辑
public <T> void show(T t) {
System.out.println("数据:" + t);
}
}
public class GenericMethodTest {
public static void main(String[] args) {
GenericUtil util = new GenericUtil();
util.show("林青霞"); // 字符串类型
util.show(30); // 整数类型
util.show(true); // 布尔类型
util.show(12.34); // 浮点类型
}
}
3.3 典型应用场景
JDK 的Collections工具类中几乎全是泛型方法,例如reverse(反转集合):
java
// JDK源码核心逻辑
public class Collections {
// 泛型方法:反转任意类型的List集合
public static <T> void reverse(List<T> list) {
int size = list.size();
for (int i = 0, j = size - 1; i < j; i++, j--) {
swap(list, i, j); // 调用泛型swap方法
}
}
// 泛型方法:交换List中任意位置的元素
public static <T> void swap(List<T> list, int i, int j) {
final List<T> l = list;
l.set(i, l.set(j, l.get(i)));
}
}
// 调用示例:适配任意类型List
List<String> strList = Arrays.asList("A", "B", "C");
Collections.reverse(strList); // 结果:[C, B, A]
List<Integer> intList = Arrays.asList(1, 2, 3);
Collections.swap(intList, 0, 2); // 结果:[3, 2, 1]
4. 泛型接口
4.1 定义格式
java
修饰符 interface 接口名<类型参数> { }
4.2 实战案例
java
// 泛型接口
interface GenericInterface<T> {
T show(T t);
}
// 实现接口时指定具体类型(Integer)
class GenericImpl implements GenericInterface<Integer> {
@Override
public Integer show(Integer t) {
return t * 2;
}
}
public class GenericInterfaceTest {
public static void main(String[] args) {
GenericInterface<Integer> impl = new GenericImpl();
Integer result = impl.show(2);
System.out.println(result); // 输出:4
// impl.show("2"); // 编译报错:类型不匹配
}
}
4.3 类型参数规范(T/E/K/V)
泛型的类型参数是占位符,有约定俗成的命名规范:
| 占位符 | 含义(英文) | 用途场景 |
|---|---|---|
| T | Type(类型) | 通用类型参数(类、接口、方法) |
| E | Element(元素) | 集合中的元素类型(如 List<E>) |
| K | Key(键) | 映射中的键类型(如 Map<K,V>) |
| V | Value(值) | 映射中的值类型(如 Map<K,V>) |
4.4 典型应用:List 接口
Java 集合框架的List接口是泛型接口的经典应用:
java
// JDK源码核心逻辑
public interface List<E> extends Collection<E> {
boolean add(E e); // 添加元素(E为元素类型)
E get(int index); // 获取元素(返回E类型)
E set(int index, E element); // 修改元素
// 其他方法...
}
// 使用示例
List<String> strList = new ArrayList<>();
strList.add("Java"); // 只能添加String类型
String str = strList.get(0); // 无需强转,直接返回String
// strList.add(123); // 编译报错:类型不匹配
5. 类型通配符(<?>)
5.1 为什么需要通配符?
泛型不支持多态,直接赋值会编译报错:
java
// 编译错误:List<Integer>不能赋值给List<Number>
List<Number> list = new ArrayList<Integer>();
为了解决 "泛型集合的父子类关系" 问题,引入类型通配符<?>。
5.2 通配符分类及用法
5.2.1 无界通配符(<?>)
- 含义:表示元素类型未知的泛型集合
- 作用:作为所有泛型集合的父类(如 List<?> 是 List<String>、List<Integer>的父类)
- 限制:不能添加元素(编译器无法确定元素类型)
java
public class WildcardTest {
public static void main(String[] args) {
List<?> list1 = new ArrayList<Object>();
List<?> list2 = new ArrayList<Number>();
List<?> list3 = new ArrayList<Integer>();
// list1.add(3); // 编译报错:无法确定元素类型,禁止添加
}
}
5.2.2 上限通配符(<? extends 类型>)
- 含义:表示 "类型及其子类"(如
<? extends Number>代表 Number或者其子类型 ) - 适用场景:读取数据(如遍历集合),不适合写入数据
java
// 语法格式
List<? extends Number> list = new ArrayList<Integer>();
// 实战案例:读取任意Number子类的集合
public void printElements(List<? extends Number> list) {
for (Number n : list) {
System.out.println(n); // 安全读取,无需强转
}
}
// 调用示例
List<Integer> intList = Arrays.asList(1, 2, 3);
List<Long> longList = Arrays.asList(10L, 20L);
printElements(intList); // 合法
printElements(longList); // 合法
5.2.3 下限通配符(<? super 类型>)
- 含义:表示 "类型及其父类"(如
<? super Integer>代表 Integer或者其父类型) - 适用场景:写入数据(如向集合添加元素),读取时只能返回 Object
java
// 语法格式
List<? super Integer> list = new ArrayList<Number>();
// 实战案例:向集合添加Integer及其子类元素
public void addIntegers(List<? super Integer> list) {
list.add(10); // 合法:Integer类型
list.add(20); // 合法:Integer类型
// list.add(30L); // 编译报错:Long不是Integer的子类
}
// 调用示例
List<Number> numList = new ArrayList<>();
addIntegers(numList); // 结果:numList = [10, 20]
5.3 JDK 源码应用:Collections.max ()
上限通配符在 JDK 源码中广泛应用,例如Collections.max()方法:
java
// JDK源码核心逻辑
public static <T extends Object & Comparable<? super T>> T max(Collection<? extends T> coll) {
Iterator<? extends T> i = coll.iterator();
T candidate = i.next();
while (i.hasNext()) {
T next = i.next();
if (next.compareTo(candidate) > 0) {
candidate = next;
}
}
return candidate;
}
// 调用示例:兼容Integer、Long等Number子类集合
List<Integer> intList = Arrays.asList(1, 2, 3);
List<Long> longList = Arrays.asList(10L, 20L);
Number max1 = Collections.max(intList); // 合法
Number max2 = Collections.max(longList); // 合法
6. 可变参数(与泛型结合)
6.1 定义格式
java
修饰符 返回值类型 方法名(数据类型... 变量名) { }
- 本质:变量是一个数组
- 规则:可变参数必须放在参数列表最后
6.2 实战案例
java
public class VarargsTest {
public static void main(String[] args) {
System.out.println(sum()); // 0
System.out.println(sum(10)); // 10
System.out.println(sum(10, 20)); // 30
System.out.println(sum(10, 20, 30));// 60
int[] intArr = {10, 20, 30, 40};
System.out.println(sum(intArr)); // 100
}
// 可变参数方法:计算任意个整数的和
public static int sum(int... a) {
int sum = 0;
for (int i : a) {
sum += i;
}
return sum;
}
}
6.3 泛型 + 可变参数的应用
JDK 工具类中大量结合泛型和可变参数,例如:
Arrays.asList(T... a):数组转集合List.of(E... elements):创建不可变 ListSet.of(E... elements):创建不可变 Set
java
// 1. 数组转集合
List<String> strList = Arrays.asList("A", "B", "C");
// 2. 创建不可变List
List<Integer> intList = List.of(1, 2, 3);
// 3. 创建不可变Set
Set<String> strSet = Set.of("X", "Y", "Z");
7. 练习:编译报错分析
题目:以下代码为什么编译报错?如何修正?
java
运行
// 报错代码1
public static void addStrings(List<? extends Object> list) {
list.add("aaa"); // 报错
list.add(new Object()); // 报错
}
// 报错代码2
public void addString(List<? super A> list) {
list.add(new A()); // 合法?
list.add(new Circle()); // 合法?(Circle继承A)
list.add(new GeometricObject()); // 报错?
list.add(new Object()); // 报错?
}
分析与修正
报错 1:List<? extends Object>
- 原因:上限通配符
<? extends Object>表示 "Object 及其子类",编译器无法确定具体类型,禁止添加任何元素(除了 null) - 修正:若需添加元素,改用下限通配符
<? super String>:
java
运行
public static void addStrings(List<? super String> list) {
list.add("aaa"); // 合法
// list.add(new Object()); // 仍报错:Object不是String的子类
}
报错 2:List<? super A>
- 规则:下限通配符
<? super A>仅允许添加A及其子类 - 修正后:
java
运行
public void addString(List<? super A> list) {
list.add(new A()); // 合法:A是本身
list.add(new Circle()); // 合法:Circle是A的子类
// list.add(new GeometricObject()); // 报错:GeometricObject是A的父类
// list.add(new Object()); // 报错:Object是A的父类
}
总结
泛型是 Java 中解决 "类型安全" 和 "代码复用" 的核心特性,核心要点:
- 泛型类 / 接口:一套代码适配多种类型(如 ArrayList)
- 泛型方法:独立于类的泛型逻辑(如 Collections 工具类)
- 类型通配符:解决泛型集合的父子类关系(<?> / <? extends T> / <? super T>)
- 可变参数:简化多参数传递,常与泛型结合使用
掌握泛型能让你的代码更安全、更简洁,尤其在集合操作和框架开发中不可或缺。建议结合 JDK 源码(如 ArrayList、Collections)深入理解,多动手实践才能熟练运用~