泛型
- 定义
- 泛型出现之前的问题
- 泛型类
- 泛型接口
- 泛型方法
- 泛型通配符
- 类型擦除
- [原始类型 List、List\<Object>、List<?> 的区别](#原始类型 List、List<Object>、List<?> 的区别)
定义
一种参数化类型机制
泛型是 Java 5 引入的一种 参数化类型 机制,允许在定义类、接口、方法时使用类型参数(如 T、E),在具体使用时才指定实际类型。泛型在编译时进行类型检查和类型擦除,生成不带泛型信息的字节码,因此属于编译时多态
泛型出现之前的问题
在泛型出现之前,Java集合类(如ArrayList)存储的是Object类型,这意味着可以向集合中添加任何类型的对象。
java
List list = new ArrayList();
list.add("Hello");
list.add(123); // 可以添加Integer,但容易引发问题
// 取出时需要强制转换
String str = (String) list.get(0); // 没问题
String error = (String) list.get(1); // 运行时抛出 ClassCastException
这段代码在编译时完全正常,但在运行时却会崩溃。问题的根源在于:编译器无法知道集合中元素的实际类型,类型安全只能靠程序员手动保证。
泛型提供了编译时类型安全检查机制,在定义类、接口、方法时可以指定类型参数
java
List<String> list = new ArrayList<>();
list.add("Hello");
// list.add(123); // 编译错误:不兼容的类型
String str = list.get(0); // 无需强制转换
泛型类
泛型类是在类名后使用尖括号 <T> 声明类型参数,类内部可以使用该类型,此时Box<String> 中的 T 被替换为 String,编译器会确保 setContent 只能接收 String 类型,getContent 返回 String,无需强转。
java
public class Box<T> {
private T content;
public void setContent(T content) {
this.content = content;
}
public T getContent() {
return content;
}
public static void main(String[] args) {
Box<String> stringBox = new Box<>();
stringBox.setContent("Hello");
String str = stringBox.getContent(); // 无需强制转换
System.out.println(str);
Box<Integer> intBox = new Box<>();
intBox.setContent(123);
Integer num = intBox.getContent();
System.out.println(num);
}
}
泛型接口
泛型接口与泛型类类似,在接口名后声明类型参数。Pair<K, V> 接口,表示键值对。
java
public interface Pair<K, V> {
K getKey();
V getValue();
}
java
public class OrderedPair<K, V> implements Pair<K, V> {
private K key;
private V value;
public OrderedPair(K key, V value) {
this.key = key;
this.value = value;
}
@Override
public K getKey() { return key; }
@Override
public V getValue() { return value; }
public static void main(String[] args) {
Pair<String, Integer> p1 = new OrderedPair<>("age", 30);
Pair<Integer, String> p2 = new OrderedPair<>(1, "One");
System.out.println(p1.getKey() + " = " + p1.getValue());
System.out.println(p2.getKey() + " = " + p2.getValue());
}
}
泛型方法
泛型方法是指方法声明时带有类型参数的方法,可以独立于类是否泛型。类型参数 <T> 放在方法返回值之前,调用时通常不需要显式指定类型,编译器会根据参数推断。
java
public class GenericMethodDemo {
// 泛型方法,<T> 声明类型参数,放在返回值之前
public static <T> void swap(T[] array, int i, int j) {
T temp = array[i];
array[i] = array[j];
array[j] = temp;
}
public static void main(String[] args) {
String[] strs = {"A", "B", "C"};
swap(strs, 0, 2); // 类型推断为 String
System.out.println(Arrays.toString(strs)); // [C, B, A]
Integer[] ints = {1, 2, 3};
swap(ints, 0, 1); // 类型推断为 Integer
System.out.println(Arrays.toString(ints)); // [2, 1, 3]
}
}
泛型通配符
泛型通配符 ? 用于表示未知的类型,在无法确定具体类型时提供灵活性。通配符主要分为三类:无界通配符、上界通配符、下界通配符。
对于下面的代码,可以发现由于泛型是不变的,List<String>并不是 List<Object> 的子类型,如果允许这样做,我们就可以向strings中添加非String类型的对象,破坏了类型安全。此时可以通过无界通配符来表示一个可以接受任何List的方法。
java
List<String> strings = new ArrayList<>();
List<Object> objects = strings; // 编译错误!
无界通配符
无界通配符 ? 表示未知类型,如下面代码可以接收任何类型的泛型集合,但不能向集合中写入元素(除了 null),因为无法确定类型安全。
java
public void printList(List<?> list) {
for (Object obj : list) {
System.out.println(obj);
// list.add("hello"); // 编译错误:不能添加元素(除null外)
// list.add(123); // 编译错误
list.add(null); // 唯一允许的添加
}
}
// 可以传入任何类型的 List
List<String> names = Arrays.asList("Alice", "Bob");
List<Integer> numbers = Arrays.asList(1, 2, 3);
printList(names); // OK
printList(numbers); // OK
上界通配符
上界通配符表示类型是 T 或 T 的子类,即< ? extends T>。例如List<? extends Number> 表示元素类型是 Number 或其子类(如 Integer、Double),除了 null,不能向集合添加任何元素,因为无法确定具体子类型
java
public class UpperBoundWildcard {
// 计算数字列表的总和(生产者:只读)
public static double sum(List<? extends Number> numbers) {
double total = 0.0;
for (Number n : numbers) { // 读取为 Number
total += n.doubleValue();
}
return total;
}
public static void main(String[] args) {
List<Integer> ints = Arrays.asList(1, 2, 3);
List<Double> doubles = Arrays.asList(1.5, 2.5, 3.5);
System.out.println(sum(ints)); // 6.0
System.out.println(sum(doubles)); // 7.5
// 不能写入
// ints.add(4); // 编译错误:不能添加元素
}
}
下界通配符
下界通配符表示类型是 T 或 T 的父类,即 <? super T>。例如List<? super Integer> 表示元素类型是 Integer 或其父类(如 Number、Object)
java
public class LowerBoundWildcard {
// 将整数列表复制到目标列表(消费者:只写)
public static void copy(List<Integer> src, List<? super Integer> dest) {
for (Integer num : src) {
dest.add(num); // 可以添加 Integer 或其子类
}
}
public static void main(String[] args) {
List<Integer> src = Arrays.asList(1, 2, 3);
List<Number> destNumber = new ArrayList<>();
List<Object> destObject = new ArrayList<>();
copy(src, destNumber); // 可以,Number 是 Integer 的父类
copy(src, destObject); // 可以,Object 是 Integer 的父类
System.out.println(destNumber); // [1, 2, 3]
// 读取时只能作为 Object
for (Object obj : destNumber) {
System.out.println(obj);
}
}
}
PECS 原则
PECS ( Producer Extends, Consumer Super ) 帮助我们决定何时使用 extends 和 super
- Producer(生产者):如果参数化类型代表一个生产者,即提供数据(只读),用 <? extends T>
- Consumer(消费者):如果参数化类型代表一个消费者,即消费数据(只写),用 <? super T>


类型擦除
在编译时提供类型检查,编译后的字节码中不保留泛型类型信息
Java 泛型在编译时提供类型检查,但编译后的字节码中不保留泛型类型信息。这个过程称为类型擦除。
- 编译器会将泛型类型中的类型参数替换为它的第一个上界(如果有 extends 指定)或 Object(如果没有指定上界)
- 插入必要的强制转换
- 当子类重写父类方法,且方法签名在擦除后出现冲突时,编译器会生成桥接方法(Bridge Method)来保持多态性。最常见的场景是类实现了泛型接口或继承了泛型类
java
//无上界的情况替换为Obcjet
//原始代码
public class Box<T> {
private T content;
public void setContent(T content) { this.content = content; }
public T getContent() { return content; }
}
//擦除后
public class Box {
private Object content;
public void setContent(Object content) { this.content = content; }
public Object getContent() { return content; }
}
java
//插入必要的强制转换
//原始代码
List<String> list = new ArrayList<>();
list.add("Hello");
String s = list.get(0); // 原本不需要强转
//擦除后
List list = new ArrayList();
list.add("Hello");
String s = (String) list.get(0); // 编译器自动插入强转
java
//生成桥接方法以保持多态性
//原始代码
public class IntegerBox implements Comparable<IntegerBox> {
private int value;
public IntegerBox(int value) {this.value = value;}
@Override
public int compareTo(IntegerBox other) {
return Integer.compare(this.value, other.value);
}
}
//Comparable<IntegerBox> 在擦除后会变成 Comparable
//接口Comparable的 compareTo 方法签名变为 compareTo(Object)
//但 IntegerBox 中实现的是 compareTo(IntegerBox),签名不匹配,多态会失效
//擦除后编译器生成的桥接方法
public int compareTo(Object other) {
return compareTo((IntegerBox) other);
}
另一个常见场景:子类重写父类方法但返回值类型不同(协变返回类型)
Parent.get() 返回 Number,Child.get() 返回 Integer,签名不匹配(返回值类型在方法重写中不重要,但在字节码层面需要匹配)。编译器会在 Child 中生成桥接方法
java
class Parent {
public Number get() { return 0; }
}
class Child extends Parent {
@Override
public Integer get() { return 100; } // 协变返回类型
}
//生成桥接方法
public Number get() { return get(); } // 调用 Integer get()
类型擦除带来的问题
- 运行时无法区分泛型类型
java
List<String> list1 = new ArrayList<>();
List<Integer> list2 = new ArrayList<>();
System.out.println(list1.getClass() == list2.getClass()); // true,都是 ArrayList
- 不能使用 instanceof 检查泛型类型(instanceof 用于判断一个对象是否是某个类、其子类或实现的接口的实例。它返回一个布尔值,表示判断结果)
java
if (list instanceof List<String>) // 编译错误
- 不能创建泛型数组
java
T[] array = new T[10]; // 编译错误
// 可以这样绕过:
List<T>[] array = (List<T>[]) new List[10]; // 但会有警告
- 静态上下文中不能使用泛型类型参数
java
public class GenericClass<T> {
private static T instance; // 编译错误
public static T getInstance() { ... } // 编译错误
}
原始类型 List、List<Object>、List<?> 的区别
- 原始类型是指不带任何泛型参数的泛型类型,编译时不进行泛型类型检查。
- List<Object>是指定类型参数为 Object 的泛型类型,编译时进行类型检查,只允许添加 Object 或其子类型的对象(实际上任何对象都可以,因为所有类都继承自 Object)。典型的由于泛型不变性, List <Object>不是 List <String> 的父类型,因此不能进行转换。
- List<?> 是未知类型的列表,类型参数为通配符 ?,表示元素类型未知。是所有 List<具体类型> 的父类型(如 List<String> 、List<Integer> 都可以赋值给 List<?>)