Java泛型案例:从零掌握基础使用的完整指南
目录导读
- 什么是Java泛型?为什么要用它?
- 泛型基础语法:类、接口、方法
- 实战案例:泛型类与泛型方法
- 类型擦除与边界限定
- 常见问题与避坑指南
- 泛型让代码更优雅
什么是Java泛型?为什么要用它?
问:没有泛型之前,Java如何处理不同类型的集合?
答:早期Java使用Object类型来容纳任意对象,但每次取出都需要强制类型转换。

List list = new ArrayList();
list.add("hello");
String s = (String) list.get(0); // 手动强转,易出错
这种方式有两个致命缺陷:类型不安全(可混入不同类型)和代码冗余(频繁强转)。
泛型(Generics) 自JDK 5引入,允许在定义类、接口、方法时使用类型参数(如<T>),实现在编译时期检查类型,避免ClassCastException,核心思想是:类型的参数化。
泛型基础语法:类、接口、方法
1 泛型类
public class Box<T> {
private T item;
public void setItem(T item) {
this.item = item;
}
public T getItem() {
return item;
}
}
使用:Box<String> box = new Box<>();(菱形语法<>在JDK 7+可省略类型推断)
2 泛型接口
public interface Processor<T> {
void process(T data);
}
实现时需指定类型:class StringProcessor implements Processor<String>
3 泛型方法
方法声明中独有类型参数,与类泛型无关:
public static <T> T getMiddle(T... args) {
return args[args.length / 2];
}
调用:String mid = getMiddle("a", "b", "c");
实战案例:泛型类与泛型方法
案例1:简化集合操作(泛型类)
// 定义一个泛型栈
public class GenericStack<E> {
private Object[] elements;
private int size = 0;
public GenericStack(int capacity) {
elements = new Object[capacity]; // 泛型数组创建需特殊处理
}
public void push(E e) {
elements[size++] = e;
}
@SuppressWarnings("unchecked")
public E pop() {
if (size == 0) throw new EmptyStackException();
E result = (E) elements[--size];
elements[size] = null; // 避免内存泄露
return result;
}
}
问:为什么不能直接new E[capacity]?
答:因为类型擦除导致运行时E被擦除为Object,直接创建泛型数组不合法,解决方案:创建Object数组后强转(如上),或使用Array.newInstance()。
案例2:泛型方法实现列表反转(泛型方法)
public static <T> void reverse(List<T> list) {
int size = list.size();
for (int i = 0; i < size / 2; i++) {
T temp = list.get(i);
list.set(i, list.get(size - 1 - i));
list.set(size - 1 - i, temp);
}
}
调用时自动推断类型:reverse(stringList);(无需显式传类型参数)
案例3:类型边界的使用(限定上限)
// 限定T必须是Number的子类
public static <T extends Number> double sumOfList(List<T> list) {
double sum = 0;
for (T t : list) {
sum += t.doubleValue();
}
return sum;
}
用extends关键字表示上界(T可以是Number或其子类),调用时限制更严格。
类型擦除与边界限定
问:运行时泛型类型信息还存在吗?
答:Java通过类型擦除实现泛型,编译后,所有类型参数被替换为它们的上界(默认为Object)。
Box<String> box = new Box<>();
编译成:Box box = new Box();(并插入必要的强转)
边界限定的作用:
<T extends Comparable<T>>:要求T实现Comparable,保证可比较- 通配符:
List<? extends Number>表示列表元素是Number及其子类(协变),List<? super Integer>表示元素是Integer及其父类(逆变)
问:List
答:运行时都是List类,泛型参数在运行时不保留,所以List<String>不能作为List<Object>使用(类型擦除后仍是不同类型参数)。
常见问题与避坑指南
问题1:泛型不能用于静态变量/方法?
答:可以,但需要独立声明泛型参数,类的类型参数不能在静态方法中使用,因为静态成员不依赖于具体实例,正确写法:
public class Util {
public static <T> T getValue(T t) { // 静态方法声明自己的<T>
return t;
}
}
问题2:何时使用通配符 vs 类型参数<T>?
规则:如果方法中多次使用同一个类型参数(如返回值、参数列表中的多个位置),用<T>;若仅使用一次,用通配符更简洁。
void printList(List<?> list)(只读取元素)void copy(List<? super T> dest, List<? extends T> src)(PECS原则:生产者用extends,消费者用super)
问题3:泛型数组创建异常
解决方案:使用ArrayList<T>代替T[],或使用@SuppressWarnings("unchecked")对强转进行压制。
问:为什么不推荐用泛型数组?
答:因为数组是协变且具体化的,而泛型是不可变且擦除的,两者语义冲突。
List<String>[] array = new List<String>[10]; // 编译错误
泛型让代码更优雅
掌握Java泛型的基础使用,意味着你能够写出类型安全、代码复用、可读性强的类库,从简单的Box<T>到复杂的<T extends Comparable<? super T>>,泛型是Java进阶的必修课,建议你动手写一个自定义泛型容器类(如Pair<T, U>),并尝试用泛型方法封装常用算法(如排序、查找),从而深入理解其精髓。
推荐练习:将学校里的“学生管理系统”用泛型重写,让一个Repository<T>能同时管理Student、Teacher等不同实体,体会泛型的强大。