Java 泛型的使用方法
泛型(Generics)是 Java 中的一种特性,允许在类、接口和方法中使用类型参数,提高代码的类型安全性和可复用性。泛型最常用于集合类(如 List<T>
、Map<K, V>
)和自定义类/方法。
1. 泛型类
定义一个可以存储任意类型 T
的类:
// 定义泛型类,T 代表类型参数
class Box<T> {
private T value;
public void set(T value) {
this.value = value;
}
public T get() {
return value;
}
}
// 使用泛型类
public class Main {
public static void main(String[] args) {
Box<String> stringBox = new Box<>();
stringBox.set("Hello");
System.out.println(stringBox.get()); // 输出: Hello
Box<Integer> intBox = new Box<>();
intBox.set(123);
System.out.println(intBox.get()); // 输出: 123
}
}
特点:
T
是一个类型参数,在使用Box<T>
时才具体化(如Box<String>
)。- 泛型避免了强制类型转换,提高了类型安全性。
2. 泛型方法
如果只需要某个方法支持泛型,而不是整个类,可以使用泛型方法:
public class Util {
// 泛型方法,T 是方法级别的类型参数
public static <T> void print(T item) {
System.out.println(item);
}
public static void main(String[] args) {
print("Hello"); // 输出: Hello
print(100); // 输出: 100
print(3.14); // 输出: 3.14
}
}
特点:
<T>
在返回值void
之前,表示该方法是泛型方法。T
的具体类型由调用时传入的参数决定。
3. 泛型接口
如果接口需要支持不同的数据类型,可以使用泛型:
// 定义泛型接口
interface DataProcessor<T> {
void process(T data);
}
// 实现泛型接口(指定类型为 String)
class StringProcessor implements DataProcessor<String> {
@Override
public void process(String data) {
System.out.println("Processing string: " + data);
}
}
// 使用泛型接口
public class Main {
public static void main(String[] args) {
DataProcessor<String> processor = new StringProcessor();
processor.process("Hello Generics!"); // 输出: Processing string: Hello Generics!
}
}
特点:
- 接口
DataProcessor<T>
可以支持不同的数据类型。 - 实现类
StringProcessor
具体化T
为String
。
4. 泛型通配符 ?
如果不关心泛型的具体类型,可以使用通配符 ?
。
4.1 ? extends T
(上界通配符)
表示T 或 T 的子类,常用于读取数据:
import java.util.*;
public class Main {
public static void printList(List<? extends Number> list) {
for (Number num : list) {
System.out.println(num);
}
}
public static void main(String[] args) {
List<Integer> intList = Arrays.asList(1, 2, 3);
List<Double> doubleList = Arrays.asList(1.1, 2.2, 3.3);
printList(intList); // 可以传 List<Integer>
printList(doubleList); // 可以传 List<Double>
}
}
解释:
? extends Number
:允许List<Integer>
、List<Double>
作为参数(因为Integer
和Double
都是Number
的子类)。- 只能读取数据,不能
list.add(100)
(编译错误)。
4.2 ? super T
(下界通配符)
表示T 或 T 的父类,常用于写入数据:
public class Main {
public static void addNumbers(List<? super Integer> list) {
list.add(100);
list.add(200);
}
public static void main(String[] args) {
List<Number> numberList = new ArrayList<>();
addNumbers(numberList); // 允许 List<Number>
List<Object> objectList = new ArrayList<>();
addNumbers(objectList); // 允许 List<Object>
}
}
解释:
? super Integer
:允许List<Integer>
的父类(如List<Number>
、List<Object>
)。- 只能添加 Integer 或其子类,但取出时类型为
Object
。
5. 限定泛型类型
可以使用 extends
关键字限制泛型类型:
// 只允许 T 继承自 Number
class MathBox<T extends Number> {
private T number;
public MathBox(T number) {
this.number = number;
}
public double square() {
return number.doubleValue() * number.doubleValue();
}
}
public class Main {
public static void main(String[] args) {
MathBox<Integer> intBox = new MathBox<>(5);
System.out.println(intBox.square()); // 输出: 25.0
MathBox<Double> doubleBox = new MathBox<>(3.2);
System.out.println(doubleBox.square()); // 输出: 10.24
// MathBox<String> strBox = new MathBox<>("Hello"); // ❌ 编译错误
}
}
解释:
T extends Number
:只允许T
是Number
及其子类(如Integer
、Double
)。number.doubleValue()
确保可以对T
进行数学运算。
6. 泛型的类型擦除
Java 泛型是伪泛型,在编译时起作用,运行时会擦除类型,变成 Object
:
import java.util.ArrayList;
public class Main {
public static void main(String[] args) {
ArrayList<String> list1 = new ArrayList<>();
ArrayList<Integer> list2 = new ArrayList<>();
System.out.println(list1.getClass() == list2.getClass()); // 输出: true
}
}
解释:
- 类型擦除:
ArrayList<String>
和ArrayList<Integer>
在运行时都是ArrayList<Object>
。 - 泛型只在编译阶段提供类型检查,运行时不保留具体类型。
总结
方式 | 说明 | 示例 |
---|---|---|
泛型类 | 适用于存储、操作不同类型的数据 | class Box<T> {} |
泛型方法 | 仅某个方法使用泛型,不影响整个类 | <T> void print(T item) {} |
泛型接口 | 适用于多个实现类可以使用不同类型 | interface DataProcessor<T> {} |
通配符 ? |
? extends T 适用于读取,? super T 适用于写入 |
List<? extends Number> |
限定泛型 | 限制泛型的范围,必须是某个类的子类 | class MathBox<T extends Number> {} |
类型擦除 | 运行时泛型信息被擦除 | List<String> → List<Object> |
泛型让 Java 代码更安全、灵活、可复用,但需要理解它的限制(如类型擦除)和最佳实践。