为什么要用Java泛型?¶
在Java中,我们经常需要处理各种数据集合,比如存储学生信息、商品信息等。如果不使用泛型,直接用Object类型存储数据,虽然灵活,但会带来两个主要问题:类型不安全和强制类型转换繁琐。
举个例子,没有泛型时,我们可能这样写:
// 没有泛型的集合,存储Object类型
List list = new ArrayList();
list.add("张三");
list.add(20); // 错误地添加了整数类型
// 取数据时需要强制转换,容易出错
String name = (String) list.get(0); // 正常
Integer age = (Integer) list.get(1); // 正常
String wrong = (String) list.get(1); // 运行时ClassCastException!
这里的问题在于,list可以存储任意类型的数据,编译器无法提前检查类型是否匹配,运行时一旦类型不匹配就会抛出异常。
泛型是什么?¶
泛型(Generics)是Java 5引入的特性,允许我们将类型作为参数传递给类、接口或方法,从而实现类型安全和代码复用。简单来说,泛型就是“参数化类型”,例如List<String>表示“只能存储String类型的列表”。
如何使用泛型?¶
1. 泛型类¶
泛型类是最基础的泛型应用,定义时在类名后用<T>声明类型参数(T是占位符,可自定义名称,如E、K、V等)。
示例:定义一个通用的“盒子”类
class Box<T> {
private T content; // 用T作为类型参数
public T getContent() {
return content;
}
public void setContent(T content) {
this.content = content;
}
}
使用泛型类:
// 存储字符串的盒子
Box<String> stringBox = new Box<>();
stringBox.setContent("Hello");
String str = stringBox.getContent(); // 无需强制转换,直接返回String
// 存储整数的盒子
Box<Integer> intBox = new Box<>();
intBox.setContent(100);
Integer num = intBox.getContent(); // 直接返回Integer
通过泛型,Box可以灵活存储任意类型的数据,且编译器会在编译时检查类型是否匹配,避免运行时错误。
2. 泛型接口¶
泛型接口与泛型类类似,在接口名后声明类型参数,实现类需指定具体类型。
示例:定义一个“生成器”接口
interface Generator<T> {
T generate(); // 接口方法返回T类型
}
// 实现泛型接口(指定具体类型)
class StringGenerator implements Generator<String> {
@Override
public String generate() {
return "Generic String";
}
}
// 使用
Generator<String> generator = new StringGenerator();
String result = generator.generate(); // 返回"Generic String"
3. 泛型方法¶
泛型方法是指方法本身带有类型参数的方法,可在普通类或泛型类中定义。
示例:定义一个通用的“打印”方法
class Util {
// 泛型方法:接受任意类型的数组,返回第一个元素
public static <T> T getFirstElement(T[] array) {
if (array.length == 0) return null;
return array[0];
}
}
// 使用
String[] strArray = {"A", "B", "C"};
String firstStr = Util.getFirstElement(strArray); // 返回"A"
Integer[] intArray = {1, 2, 3};
Integer firstInt = Util.getFirstElement(intArray); // 返回1
4. 泛型集合(最常用)¶
Java标准库中的集合类(如ArrayList、HashMap)都支持泛型,这是泛型的典型应用场景。
示例:使用泛型集合
// ArrayList存储String类型
List<String> names = new ArrayList<>();
names.add("Alice"); // 正确
// names.add(123); // 编译错误:类型不匹配,只能添加String
names.forEach(System.out::println); // 遍历输出:Alice
// HashMap存储键值对(键:String,值:Integer)
Map<String, Integer> scores = new HashMap<>();
scores.put("Alice", 90); // 正确
scores.put("Bob", 85);
// scores.put("Charlie", "95"); // 编译错误:值必须是Integer
Integer aliceScore = scores.get("Alice"); // 无需强制转换,直接返回Integer
通配符:让泛型更灵活¶
通配符<?>用于处理泛型集合的类型范围,常见的有两种:
- 上界通配符:
<? extends T>
表示集合中的元素只能是T或其子类(T为上界)。
示例:允许存储Number及其子类(Integer、Double等)
List<? extends Number> numbers = new ArrayList<Integer>();
numbers.add(new Integer(10)); // 错误!不允许添加元素
Number num = numbers.get(0); // 正确,返回Number类型
- 下界通配符:
<? super T>
表示集合中的元素只能是T或其父类(T为下界)。
示例:允许存储Integer及其父类(Number、Object等)
List<? super Integer> ints = new ArrayList<Object>();
ints.add(new Integer(10)); // 正确
Object obj = ints.get(0); // 正确,返回Object类型
泛型的核心优势¶
- 类型安全:编译时检查类型,避免运行时
ClassCastException。 - 消除强制转换:无需手动强转,代码更简洁。
- 代码复用:通过类型参数实现一套代码处理多种类型,无需重复编写逻辑。
注意事项¶
- 不能使用基本类型:泛型类型必须是引用类型(如
int需用Integer,double需用Double)。
List<int> nums = new ArrayList<>(); // 错误!需用List<Integer>
- 泛型不支持继承:
List<String>和List<Object>是平级的,不能互相赋值。
List<String> strList = new ArrayList<>();
List<Object> objList = strList; // 错误!泛型类型不可继承
- 类型擦除:运行时泛型类型会被擦除(
T被替换为Object),因此泛型类中无法直接使用T的实例化(如new T())。
总结¶
泛型是Java中解决类型安全和代码复用的重要特性,通过参数化类型让集合和类更灵活、更安全。掌握泛型的核心在于理解类型参数、泛型类/方法/接口以及通配符的使用场景,多通过简单示例练习,就能快速上手。
通过本文的示例和讲解,希望你能初步理解Java泛型的价值和用法。从简单的集合泛型开始,逐步尝试泛型类和方法,很快就能熟练运用泛型提升代码质量啦!