Java核心之泛型
Java 泛型(Generics)是 JDK 5 引入的特性,它允许在定义类、接口和方法时使用类型参数(type parameter),从而实现代码的类型安全和复用性。泛型的核心思想是 “参数化类型”,即把类型作为参数传递。
一、泛型的基本概念
在泛型出现之前,Java 集合只能存储 Object
类型,取出时需要强制类型转换,容易出现 ClassCastException
。泛型通过在编译期检查类型,避免了运行时的类型转换错误。
示例:无泛型 vs 有泛型
// 无泛型:需要强制转换,可能出错
List list = new ArrayList();
list.add("hello");
String str = (String) list.get(0); // 正确
Integer num = (Integer) list.get(0); // 运行时抛 ClassCastException// 有泛型:编译期检查类型,无需强制转换
List<String> strList = new ArrayList<>();
strList.add("hello");
String str = strList.get(0); // 无需转换,安全
strList.add(123); // 编译期报错(类型不匹配)
二、泛型的使用场景
泛型主要用于类、接口、方法的定义,分别称为泛型类、泛型接口、泛型方法。
1. 泛型类
在类定义时声明类型参数,使用 <T>
(T 为类型变量,可自定义名称,如 E、K、V 等)表示。
示例:自定义泛型类
// 泛型类:T 为类型参数(Type Parameter)
public class Box<T> {private T value;public void setValue(T value) {this.value = value;}public T getValue() {return value;}public static void main(String[] args) {// 使用时指定具体类型(Type Argument)Box<String> stringBox = new Box<>();stringBox.setValue("Hello");String str = stringBox.getValue(); // 类型安全Box<Integer> intBox = new Box<>();intBox.setValue(123);Integer num = intBox.getValue();}
}
- 类型变量
T
在创建对象时被具体类型(如String
、Integer
)替换。 - 一个泛型类可以有多个类型参数,如
Map<K, V>
(Key 和 Value 分别为两个类型参数)。
2. 泛型接口
与泛型类类似,接口定义时声明类型参数,实现类需指定具体类型或继续保留泛型。
示例:泛型接口
// 泛型接口
public interface Generator<T> {T generate();
}// 实现类:指定具体类型(String)
public class StringGenerator implements Generator<String> {@Overridepublic String generate() {return "Generated string";}
}// 实现类:保留泛型(适用于通用实现)
public class DefaultGenerator<T> implements Generator<T> {@Overridepublic T generate() {return null; // 简化示例}
}
3. 泛型方法
在方法声明时独立声明类型参数(与类的泛型无关),可以是静态方法或实例方法。
示例:泛型方法
public class GenericMethodExample {// 泛型方法:<T> 声明类型参数,T 为返回值类型public static <T> T getFirstElement(List<T> list) {if (list != null && !list.isEmpty()) {return list.get(0);}return null;}public static void main(String[] args) {List<String> strList = Arrays.asList("a", "b", "c");String firstStr = getFirstElement(strList); // 自动推断类型为 StringList<Integer> intList = Arrays.asList(1, 2, 3);Integer firstInt = getFirstElement(intList); // 自动推断类型为 Integer}
}
- 泛型方法的类型参数声明
<T>
必须在返回值类型之前。 - 调用时无需显式指定类型(编译器会自动推断)。
三、泛型通配符
泛型通配符(Wildcard)用于灵活处理泛型类型,常见的有 ?
(无界通配符)、? extends T
(上界通配符)、? super T
(下界通配符)。
1. 无界通配符 ?
表示 “任意类型”,用于不确定具体类型的场景。
示例:
public static void printList(List<?> list) {for (Object obj : list) {System.out.println(obj);}
}// 可接收任意类型的 List
printList(Arrays.asList("a", "b"));
printList(Arrays.asList(1, 2, 3));
- 注意:
List<?>
不能添加元素(除了null
),因为编译器无法确定具体类型。
2. 上界通配符 ? extends T
表示 “T
及其子类”,限制类型的上限。
示例:
// 只能接收 Number 及其子类(如 Integer、Double)
public static double sum(List<? extends Number> numbers) {double total = 0;for (Number num : numbers) {total += num.doubleValue();}return total;
}// 合法调用
sum(Arrays.asList(1, 2, 3)); // Integer 是 Number 子类
sum(Arrays.asList(1.5, 2.5)); // Double 是 Number 子类
- 上界通配符的集合只能读取(可安全转型为
T
),不能添加(无法确定具体子类类型)。
3. 下界通配符 ? super T
表示 “T
及其父类”,限制类型的下限。
示例:
// 只能接收 Integer 及其父类(如 Number、Object)
public static void addIntegers(List<? super Integer> list) {list.add(1); // 可以添加 Integer 及其子类list.add(2);
}// 合法调用
addIntegers(new ArrayList<Integer>()); // Integer 本身
addIntegers(new ArrayList<Number>()); // Number 是 Integer 父类
- 下界通配符的集合可以添加(
T
及其子类),读取时只能转型为 Object(无法确定具体父类类型)。
四、泛型擦除(Type Erasure)
Java 泛型是编译期特性,在编译后会被 “擦除”,字节码中不保留泛型类型信息,这一过程称为 “泛型擦除”。
- 擦除规则:
- 未指定边界的类型参数(如
<T>
)擦除为Object
。 - 指定上界的类型参数(如
<T extends Number>
)擦除为上界类型(Number
)。
- 未指定边界的类型参数(如
示例:擦除前后对比
// 泛型类(编译前)
public class Box<T> {private T value;public T getValue() { return value; }
}// 擦除后(编译为字节码)
public class Box {private Object value;public Object getValue() { return value; }
}
- 泛型擦除导致运行时无法获取泛型类型信息(如
list.getClass()
只能得到ArrayList.class
,而非ArrayList<String>.class
)。 - 这也是泛型数组创建受限的原因(
new ArrayList<String>[10]
编译报错)。
五、泛型的限制
- 不能用基本类型实例化泛型:泛型类型参数必须是引用类型,如
List<int>
不合法,需用List<Integer>
。 - 泛型类不能直接实例化类型参数:如
new T()
不合法(擦除后为new Object()
,失去意义)。 - 不能声明静态泛型字段:静态成员属于类,而泛型类型参数随实例变化。
- 泛型类不能扩展 Throwable:如
class MyException<T> extends Exception
不合法(异常处理需要确定的类型)。
六、泛型的优势
- 类型安全:编译期检查类型,避免运行时
ClassCastException
。 - 代码复用:一套代码可适配多种类型(如
ArrayList
可存储任意类型)。 - 可读性:代码中明确指定类型,无需注释即可知晓集合存储的元素类型。
总结
泛型通过参数化类型实现了类型安全和代码复用,是 Java 集合框架、框架开发(如 Spring、MyBatis)的基础。掌握泛型的核心概念(泛型类、接口、方法)和通配符的使用,能写出更健壮、灵活的代码。尽管泛型擦除带来了一些限制,但仍是 Java 中不可或缺的重要特性。