什么是泛型?
泛型(Generic)是Java 5引入的一个重要特性,它的本质是参数化类型。简单来说,泛型就是给类型写参数,就像方法接收参数一样。泛型让你编写更通用、更安全的代码。
生活比喻
想象你要寄送包裹。没有泛型之前,你可能需要一个"字符串包裹盒"专门装字符串,一个"整数包裹盒"专门装整数...这太浪费了!泛型就像一个万能包裹盒,你告诉它你要装什么类型,它就能完美适配——既安全又高效。
没有泛型的问题
在Java 5之前,集合(如ArrayList)只能存储Object类型。这意味着你可以把任何对象放进去,但取出来时需要强制类型转换,这会带来两个严重问题:
问题1:类型安全问题
编译时不会报错,运行时才知道类型错误。想象你在处理用户数据时突然收到一个完全意想不到的类型...
// 没有泛型的时代
List list = new ArrayList();
list.add("Hello");
list.add(123); // 编译器不会报错
// 取出来时问题来了
String s = (String) list.get(1); // 运行时ClassCastException!
System.out.println(s.length()); // 这行根本不会执行到
问题2:代码繁琐
每次从集合取元素都需要强制类型转换,代码又臭又长。
使用泛型的优势
类型安全
编译时就能发现类型错误,而不是等到运行时才崩溃
消除强制类型转换
代码更简洁,编译器自动插入类型转换代码
代码复用
一套泛型代码可以处理多种类型
泛型基本语法
1. 泛型类
// 定义泛型类 - T是类型参数
class Box {
private T value;
public void set(T value) {
this.value = value;
}
public T get() {
return value;
}
}
// 使用时指定具体类型
Box stringBox = new Box<>();
stringBox.set("Hello");
String s = stringBox.get(); // 不需要强制转换!
Box intBox = new Box<>();
intBox.set(123);
Integer num = intBox.get(); // 类型安全!
2. 泛型接口
// 定义泛型接口
interface Container {
void add(E element);
E get(int index);
int size();
}
// 实现泛型接口
class StringContainer implements Container {
private String[] array = new String[10];
private int size = 0;
@Override
public void add(String element) {
array[size++] = element;
}
@Override
public String get(int index) {
return array[index];
}
@Override
public int size() {
return size;
}
}
3. 泛型方法
// 泛型方法 - 类型参数在返回值前面
public static T getFirst(T[] array) {
if (array == null || array.length == 0) {
return null;
}
return array[0];
}
// 使用
String[] names = {"Alice", "Bob"};
Integer[] nums = {1, 2, 3};
String first = getFirst(names); // 返回 String
Integer firstNum = getFirst(nums); // 返回 Integer
类型参数的命名约定
Java泛型有几种常见的类型参数命名,虽然你可以用任何标识符,但遵循约定让你的代码更易读:
| 字母 | 含义 | 使用场景 |
|---|---|---|
| E | Element | 集合中的元素 |
| K | Key | Map的键 |
| V | Value | Map的值 |
| T | Type | 通用类型 |
| N | Number | 数字类型 |
| R | Result | 返回类型 |
| S, U, V | 第二、第三、第四类型 | 多类型参数 |
多类型参数
泛型可以定义多个类型参数,就像Map有K(键)和V(值):
class Pair {
private K key;
private V value;
public Pair(K key, V value) {
this.key = key;
this.value = value;
}
public K getKey() { return key; }
public V getValue() { return value; }
}
// 使用
Pair entry = new Pair<>("年龄", 25);
System.out.println(entry.getKey() + ": " + entry.getValue());
泛型在哪里使用?
Java标准库中有大量使用泛型的例子,你每天都在使用:
1
集合框架
List<String> list = new ArrayList<>();
Map<String, Integer> map = new HashMap<>();
Set<Integer> set = new HashSet<>();
2
Stream API
List<String> names = people.stream()
.map(Person::getName)
.collect(Collectors.toList());
3
Optional
Optional<String> optional = Optional.of("value");
String value = optional.orElse("default");
泛型的局限
重要事实:泛型只在编译时有效
泛型信息只存在于编译阶段,运行时会被"擦除"(这叫做类型擦除,我们后面会详细讲解)。所以你不能通过反射在运行时获取泛型的类型参数。
// 这个操作在编译时会报错
List stringList = new ArrayList<>();
List intList = new ArrayList<>();
// 虽然stringList和intList内容不同,但它们运行时类型都是ArrayList
System.out.println(stringList.getClass() == intList.getClass()); // true!
小结
- 泛型是Java 5引入的参数化类型特性,让代码更通用、更安全
- 泛型提供了编译时类型安全,避免ClassCastException
- 使用<T>语法定义类型参数
- 泛型消除强制类型转换,让代码更简洁
- 泛型支持类、接口、方法的定义
- 遵循命名约定:E、K、V、T等