🌟 1. 泛型的本质
泛型就是 “在定义时不写死具体类型,而是在用的时候再指定类型” 。
想象一个可以放任何东西的盒子 Box:
- 以前(没有泛型)Java只知道它能放
Object,取出来还要自己转换类型; - 现在(有泛型)Java让你定义
Box<T>,在用的时候给它一个类型:Box<String>或Box<Integer>。
这样,编译器能检查类型安全,防止把奇怪的东西放进盒子,还能省去繁琐的类型转换。
🌟 2. 泛型的细节
✅ 2.1 泛型类
定义:在类名后面加 <T>,T 是类型形参:
public class Box<T> {
private T content;
public void set(T content) {
this.content = content;
}
public T get() {
return content;
}
}
使用:
Box<String> box1 = new Box<>();
box1.set("Hello"); // 编译器知道T是String
String str = box1.get(); // 不需要强制类型转换
Box<Integer> box2 = new Box<>();
box2.set(123); // 编译器知道T是Integer
Integer num = box2.get();
🔑 重点:
Box<String>和Box<Integer>是两种不同的泛型实例。- 泛型检查发生在编译期,不会影响运行时性能。
✅ 2.2 泛型方法
在方法返回类型前加 <T> 表示这是个泛型方法:
public class Utils {
public static <T> T identity(T item) {
return item;
}
}
使用:
String s = Utils.identity("abc"); // 编译器推断T=String
Integer i = Utils.identity(100); // 编译器推断T=Integer
✅ 2.3 泛型接口
可以用在接口上,让实现类指定泛型:
public interface Generator<T> {
T next();
}
public class StringGenerator implements Generator<String> {
public String next() {
return "hello";
}
}
🌟 3. 泛型的高级用法:通配符
有时候你并不关心具体是什么类型,而是想说:
- “我能接受任何类型”,就用
<?> - “我能接受某个类型的子类”,用
<? extends T> - “我能接受某个类型的父类”,用
<? super T>
例子:
public static void printList(List<?> list) {
for (Object elem : list) {
System.out.println(elem);
}
}
public static void processNumbers(List<? extends Number> nums) {
// 可以读取Number及其子类
Number n = nums.get(0);
// nums.add(123); // ❌ 不允许写入,因为可能是 List<Double>,加Integer不安全
}
public static void addIntegers(List<? super Integer> ints) {
ints.add(42); // ✅ 可以写入Integer及其子类
}
🌟 4. 泛型常见错误
1️⃣ 不能用基本类型做泛型参数:
// List<int> list; // ❌ 不允许
List<Integer> list; // ✅ 必须用包装类
2️⃣ 不能直接创建泛型数组:
// T[] array = new T[10]; // ❌ 不允许
Object[] array = new Object[10]; // ✅
🌟 5. 实际例子演示:一个泛型工具类
定义一个交换数组中两个位置元素的泛型方法
public class ArrayUtils {
public static <T> void swap(T[] arr, int i, int j) {
T tmp = arr[i];
arr[i] = arr[j];
arr[j] = tmp;
}
}
使用:
public class Test {
public static void main(String[] args) {
String[] words = {"apple", "banana", "cherry"};
ArrayUtils.swap(words, 0, 2);
// words现在是 {"cherry", "banana", "apple"}
Integer[] numbers = {1, 2, 3};
ArrayUtils.swap(numbers, 1, 2);
// numbers现在是 {1, 3, 2}
}
}
🔑 这个例子显示:
- 不管是
String[]还是Integer[],swap()都可以用; - 编译器在使用时知道
T是具体类型,保证类型安全。
🌟 6. 泛型类型擦除
-
泛型只存在于编译期,编译后字节码里泛型信息会被擦除。
-
比如:
List<String> list1 = new ArrayList<>(); List<Integer> list2 = new ArrayList<>(); System.out.println(list1.getClass() == list2.getClass()); // true -
运行时 JVM 看到的都是
ArrayList,并不区分String和Integer。
✅ 小结一句话
泛型=写代码时不指定具体类型,用的时候告诉编译器是什么类型,让编译器帮你做类型检查,避免强制类型转换出错。