Java 泛型

背景与动机#

泛型用于把“类型”变成可参数化的东西,让代码在保持复用的同时获得编译期类型检查。

没有泛型时,集合里取出的值往往需要强制转换;有了泛型,编译器能提前知道集合中应该放什么类型。

核心原理拆解#

1. 集合泛型#

List<String> names = new ArrayList<>();
names.add("Tom");
String name = names.get(0);

List<String> 表示这个列表里应该保存 String。如果尝试放入其他类型,编译器会报错。

2. 泛型类#

class Box<T> {
private T value;
void set(T value) {
this.value = value;
}
T get() {
return value;
}
}

T 是类型参数,创建对象时再指定具体类型:

Box<String> box = new Box<>();
box.set("hello");

3. 泛型方法#

static <T> T first(T[] array) {
return array[0];
}

方法级别的 <T> 写在返回值类型前面,表示这个方法自己声明了一个类型参数。

4. 通配符 ?#

static void printAll(List<?> list) {
for (Object item : list) {
System.out.println(item);
}
}

List<?> 表示某种未知元素类型的列表,适合只读遍历。

5. 类型擦除#

Java 泛型主要在编译期提供类型检查,运行期很多泛型信息会被擦除。

这意味着泛型不是为每个类型生成一份全新的运行时代码,而是通过编译器检查和必要的类型转换来保证类型安全。

img
img

最小可运行代码示例#

import java.util.ArrayList;
import java.util.List;
public class GenericDemo {
public static void main(String[] args) {
Box<String> box = new Box<>();
box.set("hello");
System.out.println(box.get());
List<Integer> numbers = new ArrayList<>();
numbers.add(1);
numbers.add(2);
printAll(numbers);
}
static void printAll(List<?> list) {
for (Object item : list) {
System.out.println(item);
}
}
}
class Box<T> {
private T value;
void set(T value) {
this.value = value;
}
T get() {
return value;
}
}

常见陷阱与错误示例#

1. 使用原始类型丢失类型安全#

错误示例:

List list = new ArrayList();
list.add("java");
list.add(123);

原始类型绕开了泛型检查,后续取值时容易出现类型转换错误。

2. 误以为 List<Object> 可以接收 List<String>#

List<String> strings = new ArrayList<>();
// List<Object> objects = strings; // 编译失败

List<String> 不是 List<Object> 的子类型。需要表达未知类型时,可以使用 List<?>

3. 在 List<?> 中随意添加元素#

List<?> 的元素类型未知,除了 null 外,通常不能安全添加具体元素。

面试高频问题#

1. 泛型解决什么问题#

泛型让类型成为参数,提升代码复用,同时把类型错误尽量提前到编译期发现。

2. 什么是类型擦除#

Java 泛型主要在编译期生效,运行期很多泛型类型信息会被擦除,以保持与早期 Java 的兼容。

3. List<?>List<Object> 有什么区别#

List<?> 表示某种未知类型的列表,适合读取;List<Object> 表示元素类型就是 Object 的列表,不能直接接收 List<String>

一句话总结#

Java 泛型用类型参数提升复用和类型安全,但要记住它主要是编译期机制,运行期存在类型擦除。

文章目录

文章目录