Java 5 引入的核心特性,允许在定义类、接口、方法时使用类型参数,带来了 编译时类型检查 和 消除强制类型转换 两大好处。
为什么需要泛型?
- 类型不安全:可以向集合中添加任意类型,导致运行时转型异常。
- 代码繁琐:每次取出元素都需要手动强制转换。
// java5之前:
List list = new ArrayList();
list.add("hello");
list.add(123); // 可以混入不同类型
String s = (String) list.get(0); // 需要强制转换
String t = (String) list.get(1); // 运行时抛出 ClassCastException
//java5之后
List<String> list = new ArrayList<>();
list.add("hello");
// list.add(123); // 编译错误!只能添加 String
String s = list.get(0); // 自动转型,无需强制转换
基本语法
类型参数通常用单个大写字母表示(非强制,但为惯例)
E– Element(用于集合)T– Type(任意类型)K– Key(键)V– Value(值)N– Number?– 通配符(未知类型)
泛型类
静态成员不能使用类的泛型参数(因为静态成员在类加载时存在,而具体类型未确定)。
public class GenericClassDemo {
// ========== 1. 单类型参数泛型类 ==========
static class Box<T> {
private T content;
public void set(T content) {
this.content = content;
}
public T get() {
return content;
}
public boolean isEmpty() {
return content == null;
}
@Override
public String toString() {
return "Box{" + content + '}';
}
}
// ========== 2. 多类型参数泛型类 ==========
static class Pair<K, V> {
private K key;
private V value;
public Pair(K key, V value) {
this.key = key;
this.value = value;
}
public K getKey() {
return key;
}
public V getValue() {
return value;
}
public void setKey(K key) {
this.key = key;
}
public void setValue(V value) {
this.value = value;
}
@Override
public String toString() {
return "(" + key + ", " + value + ')';
}
}
// ========== 3. 演示使用 ==========
public static void main(String[] args) {
// ----- 单类型参数 Box 的使用 -----
Box<String> stringBox = new Box<>();
stringBox.set("Hello, generics!");
System.out.println("String box contains: " + stringBox.get());
Box<Integer> intBox = new Box<>();
intBox.set(100);
System.out.println("Integer box contains: " + intBox.get());
// ----- 多类型参数 Pair 的使用 -----
Pair<String, Integer> agePair = new Pair<>("Alice", 28);
System.out.println("Pair: " + agePair);
System.out.println("Key: " + agePair.getKey() + ", Value: " + agePair.getValue());
Pair<Double, Boolean> flagPair = new Pair<>(3.14159, true);
System.out.println("Another pair: " + flagPair);
// ----- 结合使用:将 Pair 放入 Box 中 -----
Box<Pair<String, Integer>> boxWithPair = new Box<>();
Pair<String, Integer> student = new Pair<>("Bob", 22);
boxWithPair.set(student);
System.out.println("Box containing a pair: " + boxWithPair.get());
System.out.println("Unpacked pair key: " + boxWithPair.get().getKey());
System.out.println("Unpacked pair value: " + boxWithPair.get().getValue());
}
}
泛型接口
public interface Comparable<T> {
int compareTo(T other);
}
// 实现时需要指定具体类型
public class Person implements Comparable<Person> {
@Override
public int compareTo(Person other) {
return this.age - other.age;
}
}
泛型方法
泛型方法可以定义在普通类或泛型类中,其类型参数位于返回值之前。
public class GenericMethodDemo {
// 泛型方法,使用自己的类型参数 <T>
public static <T> T getMiddle(T... a) {
return a[a.length / 2];
}
public static void main(String[] args) {
String middle = getMiddle("a", "b", "c"); // T 被推断为 String
System.out.println(middle); // b
}
}
类型擦除
Java 泛型是通过类型擦除实现的,即在编译后,类型参数会被替换为原始类型(Raw Type)。
- 无边界类型参数替换为
Object - 有边界类型参数替换为第一个边界类型
List<String> list = new ArrayList<>();
// 编译后字节码中,List<String> 变为 List
影响:
- 运行时无法获取泛型的具体类型(例如
list instanceof List<String>不合法) - 泛型类的静态变量在所有实例间共享(因为只有一份原始类)
- 泛型不能用于基本类型(
List<int>错误,需使用包装类List<Integer>)
通配符— ?
当你不确定或不关心泛型的具体类型时,使用 ? 表示未知类型。
无限定通配符 ?
public static void printList(List<?> list) {
for (Object obj : list) { // 只能当作 Object 读取
System.out.println(obj);
}
// list.add("hello"); // 编译错误!不能添加任何元素(null 除外)
}
List<?> 可以匹配任何类型的 List,但不能向其中添加元素(除了 null),因为类型未知,无法保证类型安全。
上界通配符? extends T
表示类型是 T 或 T 的子类(T 为上界)。
public static double sumOfList(List<? extends Number> list) {
double sum = 0.0;
for (Number num : list) {
sum += num.doubleValue();
}
return sum;
}
// 可以传入 List<Integer>, List<Double>, List<Number>
- 可以读取为
T类型(安全,因为任何子类都可视为T)。 - 不能写入(除了
null),因为具体子类型未知(例如不能往List<? extends Number>中添加Integer,因为它可能实际是List<Double>)。
下界通配符? super T
表示类型是 T 或 T 的父类(T 为下界)。
public static void addNumbers(List<? super Integer> list) {
list.add(123); // 可以添加 Integer 及其子类(实际上 Integer 没有子类)
list.add(456);
// Integer obj = list.get(0); // 编译错误!读取时只能得到 Object
}
- 可以写入
T或T的子类型(安全,因为? super T一定能容纳T)。 - 读取时只能获取
Object,因为实际类型可能是T的任意父类。
PECS 原则
Producer Extends, Consumer Super
- Producer:如果你只需要从集合中读取数据(生产),使用
? extends T。 - Consumer:如果你只需要向集合中写入数据(消费),使用
? super T。 - 同时读写则不应使用通配符,直接使用具体类型。
// 生产者:读取数据
public static <T> void copy(List<? extends T> src, List<? super T> dest) {
for (T item : src) {
dest.add(item);
}
}
类型边界
可以限制类型参数必须是某个类的子类或实现某些接口。
// 限定 T 必须是 Number 的子类,并且实现了 Comparable
public static <T extends Number & Comparable<T>> T max(T[] arr) {
T max = arr[0];
for (T val : arr) {
if (val.compareTo(max) > 0) max = val;
}
return max;
}
- 使用
extends关键字,后面可跟类或接口,用&连接多个接口。 - 类必须放在第一位(如果有)。
泛型与继承
List<String>不是List<Object>的子类型,即使String是Object的子类。
否则以下代码会破坏类型安全:
List<String> strings = new ArrayList<>();
List<Object> objects = strings; // 假设合法
objects.add(123); // 导致 strings 中含有 Integer
- 但可以使用通配符实现协变:
List<String>是List<? extends Object>的子类型。