前面了解到,
java基础代码
其实也就是处理一堆数据。
那其实有个bug。
就是,只能传类型确定的数据
我们在写一段代码的时候。
如果想传递和接受,不确定类型的数据,应该怎么办
于是发明了泛型这个概念。
你理解得很准!泛型(Generics) 就是为了解决"代码写死类型、无法复用"这个问题而生的。
痛点演示:没有泛型时怎么办?
你想写一个打印任意类型数组的方法:
java
// 只能打印String数组
public static void printString(String[] arr) { ... }
// 只能打印Integer数组
public static void printInteger(Integer[] arr) { ... }
// 只能打印Double数组 → 还得再写一个... 无穷无尽
有人会说:用 Object 不就行了?
java
public static void print(Object[] arr) {
for (Object o : arr) {
System.out.println(o);
}
}
但有问题:
- ❌ 取出元素必须强制转型 ,容易
ClassCastException - ❌ 编译期不检查类型,错误推迟到运行期
- ❌ 没有"这个数组里只能是同一种类型"的约束
泛型解决方案
把类型也当作一个"参数"传进去 ,用 <T> 表示:
java
// T 代表"待定的类型"
public static <T> void print(T[] arr) {
for (T t : arr) {
System.out.println(t);
}
}
// 使用时,自动推导类型
String[] strs = {"a", "b"};
Integer[] nums = {1, 2, 3};
print(strs); // 编译器理解为 print<String>
print(nums); // 编译器理解为 print<Integer>
对比效果
| 用 Object | 用泛型 | |
|---|---|---|
| 编译时类型检查 | ❌ 无 | ✅ 有 |
| 取出元素需强转 | ✅ 需要 | ❌ 不需要 |
| 代码复用度 | 一般 | 高 |
| 类型安全 | 低 | 高 |
泛型最经典的例子:集合类
java
// 没有泛型时(旧代码),一个篮子啥都能装
List list = new ArrayList();
list.add("hello");
list.add(123); // 混入不同类型
String s = (String) list.get(0); // 强转,第2个元素取出来就报错
// 有泛型后,明确约束
List<String> list2 = new ArrayList<>();
list2.add("hello");
// list2.add(123); // 编译错误!类型不匹配
String s2 = list2.get(0); // 无需强转
泛型的核心概念
1. 泛型类/接口
java
class Box<T> {
private T content;
public void set(T t) { content = t; }
public T get() { return content; }
}
Box<String> box = new Box<>(); // 这里的String是"类型实参"
2. 泛型方法(刚才展示的)
3. 类型通配符 ?
当你不确定具体类型,只想表达"某种类型"时:
java
public void printList(List<?> list) { // 可以接收 List<String> / List<Integer> ...
for (Object o : list) System.out.println(o);
}
4. 上限 extends / 下限 super
java
// 只允许 Number 及其子类
List<? extends Number> nums;
// 只允许 Integer 及其父类
List<? super Integer> ints;
一个更贴近你之前"数据处理"的例子
假设你要写一个"数据处理器",处理不同类型的数值:
java
// 泛型接口
interface Processor<T> {
T process(T input);
}
// String 处理器
class StringReverse implements Processor<String> {
public String process(String s) {
return new StringBuilder(s).reverse().toString();
}
}
// Integer 处理器
class IntegerDouble implements Processor<Integer> {
public Integer process(Integer n) {
return n * 2;
}
}
// 使用
Processor<String> p1 = new StringReverse();
String result = p1.process("abc"); // "cba"
泛型在 Java 中的"小缺点"(相对于 C++ 模板)
类型擦除:泛型只在编译期检查,运行时会擦掉类型信息。
java
List<String> list1 = new ArrayList<>();
List<Integer> list2 = new ArrayList<>();
// 下面这行会输出 true,因为运行时都是 ArrayList
System.out.println(list1.getClass() == list2.getClass());
为什么?为了兼容旧版本 Java(1.4 之前没有泛型)。
总结
| 你的问题 | 泛型的答案 |
|---|---|
| 只能传类型确定的数据 | 用 <T> 把类型参数化 |
| 代码写死类型 | 类型变成可传入的"参数" |
| 需要强制转型 | 编译期自动补上,类型安全 |
一句话记忆:泛型 = 类型的参数化,让你写出"处理任意类型数据的通用代码",同时保留编译时的类型检查。