Java 泛型(Generics)
Java 泛型(Generics)是 Java SE 5 引入的一个特性,它允许在 类、接口和方法 中使用 类型参数,以提供更高的类型安全性和代码复用性。
1. 为什么需要泛型?
在 Java 1.5 之前,使用集合(如 ArrayList)时,存储的是 Object 类型,需要手动进行类型转换:
import java.util.ArrayList;
public class Test {
public static void main(String[] args) {
ArrayList list = new ArrayList();
list.add("Hello");
list.add(123); // 不安全
String str = (String) list.get(0); // 需要强制转换
System.out.println(str);
}
}
缺点:
- 类型不安全(可以向
ArrayList添加任意类型的对象)。 - 需要类型转换,容易导致
ClassCastException。
解决方案:使用泛型
import java.util.ArrayList;
public class Test {
public static void main(String[] args) {
ArrayList<String> list = new ArrayList<>();
list.add("Hello");
// list.add(123); // 编译错误
String str = list.get(0); // 不需要强制转换
System.out.println(str);
}
}
2. 泛型的基本使用
2.1 泛型类
泛型类在定义时指定一个或多个类型参数:
// T 代表一个类型参数
class Box<T> {
private T value;
public void setValue(T value) {
this.value = value;
}
public T getValue() {
return value;
}
}
public class Test {
public static void main(String[] args) {
Box<String> stringBox = new Box<>();
stringBox.setValue("Hello");
System.out.println(stringBox.getValue());
Box<Integer> intBox = new Box<>();
intBox.setValue(100);
System.out.println(intBox.getValue());
}
}
总结:
T代表泛型类型,可以替换为任何引用类型。- 在实例化对象时,指定具体的类型,如
Box<String>。
2.2 泛型方法
泛型方法可以定义在普通类或泛型类中:
class Util {
// 泛型方法
public static <T> void print(T data) {
System.out.println(data);
}
}
public class Test {
public static void main(String[] args) {
Util.print("Hello"); // 自动推断 T 为 String
Util.print(100); // 自动推断 T 为 Integer
Util.print(3.14); // 自动推断 T 为 Double
}
}
总结:
- 泛型方法在返回类型前声明
<T>。 - Java 自动推断 类型,无需手动指定。
2.3 泛型接口
泛型接口允许指定类型:
// 定义一个泛型接口
interface Container<T> {
void add(T item);
T get();
}
// 实现泛型接口(指定具体类型)
class StringContainer implements Container<String> {
private String item;
public void add(String item) {
this.item = item;
}
public String get() {
return item;
}
}
// 实现泛型接口(保留泛型)
class GenericContainer<T> implements Container<T> {
private T item;
public void add(T item) {
this.item = item;
}
public T get() {
return item;
}
}
public class Test {
public static void main(String[] args) {
StringContainer sc = new StringContainer();
sc.add("Hello");
System.out.println(sc.get());
GenericContainer<Integer> gc = new GenericContainer<>();
gc.add(100);
System.out.println(gc.get());
}
}
总结:
- 泛型接口可以在实现时指定具体类型,也可以在实现类中继续使用泛型。
3. 泛型的类型限制(上界 & 下界)
3.1 上界通配符 <? extends T>
用于限制类型必须是 T 或 T 的子类,常用于 读取数据 的场景:
import java.util.*;
public class Test {
public static void printList(List<? extends Number> list) {
for (Number n : list) {
System.out.println(n);
}
}
public static void main(String[] args) {
List<Integer> intList = Arrays.asList(1, 2, 3);
List<Double> doubleList = Arrays.asList(3.14, 2.71);
printList(intList); // OK,Integer 是 Number 子类
printList(doubleList); // OK,Double 是 Number 子类
}
}
规则:
? extends Number表示泛型类型必须是Number或其子类。- 但不能向
list添加新元素,因为 Java 不能确定具体的子类类型。
3.2 下界通配符 <? super T>
用于限制类型必须是 T 或 T 的父类,常用于 写入数据 的场景:
import java.util.*;
public class Test {
public static void addNumbers(List<? super Integer> list) {
list.add(10);
list.add(20);
// list.add(3.14); // 编译错误
}
public static void main(String[] args) {
List<Number> numList = new ArrayList<>();
addNumbers(numList); // OK,Number 是 Integer 父类
List<Object> objList = new ArrayList<>();
addNumbers(objList); // OK,Object 是 Integer 父类
}
}
规则:
? super Integer表示泛型类型必须是Integer或其父类(如Number或Object)。- 可以向
list添加Integer或其子类,但不能保证读取时的具体类型。
4. 类型擦除(Type Erasure)
Java 的泛型是类型擦除的,即编译时检查类型,运行时移除泛型信息。
public class Test {
public static void main(String[] args) {
List<String> list1 = new ArrayList<>();
List<Integer> list2 = new ArrayList<>();
System.out.println(list1.getClass() == list2.getClass()); // true
}
}
注意:
List<String>和List<Integer>在编译后都变成List<Object>,无法在运行时区分。
5. 不能使用泛型的情况
- 基本数据类型不能作为泛型类型(需使用包装类,如
int->Integer)。 - 静态方法或变量不能使用类的泛型参数。
- 不能创建泛型数组(如
T[] array = new T[10];,需使用Object[])。 - 不能实例化泛型类型(如
new T();)。
6. 总结
| 泛型特性 | 作用 |
|---|---|
| 泛型类 | 定义时使用泛型,提高代码复用性 |
| 泛型方法 | 方法级别的泛型,独立于类 |
| 泛型接口 | 使接口支持泛型类型 |
上界 <? extends T> | 适用于读取数据,不能写入 |
下界 <? super T> | 适用于写入数据,不能安全读取 |
| 类型擦除 | 泛型信息在运行时被擦除 |
更多详细内容请关注其他相关文章。