Java基础 - 泛型

Introduction

范型的作用是在编译时推断参数的类型,提高代码重用性,并且将类型安全检查从 “运行期” 提前到 “编译期”,从而写出更安全。
在Java引入范型之前如果要定义一个通用容器,比如List:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
import java.util.ArrayList;
import java.util.List;

List list = new ArrayList();

// 什么都能放进去,没有类型限制
list.add("Hello");
list.add(123);
list.add(new java.util.Date());

System.out.println("list.get(0) = " + list.get(0));

// 取出时必须强制类型转换
String firstItem = (String) list.get(0);

// RuntimeException: 编译时完全正常,因为编译器不知道list里第二个元素不是String
String secondItem = (String) list.get(1); // 程序崩溃

范型避免了这种“类型不匹配的错误在编译时无法被发现,只能等到代码运行时才暴露出来”的问题。

Generic Class

范型允许我们为类、接口和方法定义一个或多个类型参数。
语法:class ClassName<T1, T2, ...> { /* ... */ }

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
// T 是一个类型参数,代表 "Type"
public class Box<T> {
private T item;

public void set(T item) {
this.item = item;
}

public T get() {
return this.item;
}
}

// 使用泛型类
public class GenericTypeExample {
public static void main(String[] args) {
// 实例化时,将 T 指定为 String
Box<String> stringBox = new Box<>();
stringBox.set("Hello, Generics!");
// stringBox.set(123); // 编译错误!类型不匹配

String content = stringBox.get();
System.out.println(content);

// 实例化时,将 T 指定为 Integer
Box<Integer> integerBox = new Box<>();
integerBox.set(42);
int number = integerBox.get();
System.out.println(number);
}
}
  • stringBox 在编译时就被限定只能存储 String 类型,任何试图存入其他类型的操作都会导致编译失败。
  • 从 stringBox.get() 返回的就是 String 类型,无需进行类型转换

Generic Methods

泛型方法在编写静态工具类时尤其有用。
语法:public <T> ReturnType methodName(T parameter) { /* ... */ }

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
public class ArrayUtils {

// 一个泛型方法,用于交换数组中的两个元素
public static <T> void swap(T[] array, int i, int j) {
T temp = array[i];
array[i] = array[j];
array[j] = temp;
}

// 另一个泛型方法,返回数组的中间元素
public static <E> E getMiddleElement(E[] array) {
if (array == null || array.length == 0) {
return null;
}
return array[array.length / 2];
}
}

// 使用泛型方法
public class GenericMethodExample {
public static void main(String[] args) {
String[] names = {"Alice", "Bob", "Charlie"};
ArrayUtils.swap(names, 0, 2); // 编译器会自动推断 T 是 String
System.out.println(java.util.Arrays.toString(names)); // 输出: [Charlie, Bob, Alice]

Integer[] numbers = {1, 2, 3, 4, 5};
Integer middleNumber = ArrayUtils.getMiddleElement(numbers); // E 被推断为 Integer
System.out.println(middleNumber); // 输出: 3
}
}
  • <T> 的作用域仅限于该方法本身
  • 类型参数 <T> 声明在方法的返回类型之前
  • 范型方法允许在调用时动态指定类型,而不是固定使用某种类型

Generic Interface

Comparable<T> 接口就是一个典型的泛型接口。

1
2
3
4
5
6
7
8
9
10
11
12
13
public interface Comparable<T> {
public int compareTo(T o);
}

public class Student implements Comparable<Student> { // 指定 T 就是 Student
private String name;
private int age;

@Override
public int compareTo(Student other) { // 参数直接就是 Student 类型,无需强制转换
return this.age - other.age;
}
}

Wildcards

上界通配符(? extends T)

未知类型 (?) 是 T 或 T 的某个子类型。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
import java.util.List;
import java.util.Arrays;

public class UpperBoundWildcard {

// 使用上界通配符,表示这个 List 包含的是 Number 或者 Number 的某个子类
public static double sumOfList(List<? extends Number> list) {
double sum = 0.0;
for (Number n : list) {
// 我们可以安全地从中读取数据,并转型为 Number
sum += n.doubleValue();
}
// list.add(Integer.valueOf(1)); // 编译错误!
return sum;
}

public static void main(String[] args) {
List<Integer> intList = Arrays.asList(1, 2, 3);
System.out.println("Sum = " + sumOfList(intList)); // 合法

List<Double> doubleList = Arrays.asList(1.1, 2.2, 3.3);
System.out.println("Sum = " + sumOfList(doubleList)); // 合法
}
}
  • 这里的addNumbers函数可以接收List<Integer>, List<Double>等,只要是Number的子类.

下界通配符 (? super T)

未知类型 (?) 是 T 或 T 的某个父类型。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
import java.util.List;
import java.util.ArrayList;

public class LowerBoundWildcard {

// 使用下界通配符,表示这个 List 的类型是 Integer 或是 Integer 的某个父类
public static void addNumbers(List<? super Integer> list) {
for (int i = 1; i <= 3; i++) {
// 我们可以安全地向其中添加 Integer 对象
list.add(i);
}
// Object obj = list.get(0); // 读取时只能保证是 Object 类型
}

public static void main(String[] args) {
List<Integer> intList = new ArrayList<>();
addNumbers(intList);
System.out.println(intList); // 输出: [1, 2, 3]

List<Number> numList = new ArrayList<>();
addNumbers(numList);
System.out.println(numList); // 输出: [1, 2, 3]

List<Object> objList = new ArrayList<>();
addNumbers(objList);
System.out.println(objList); // 输出: [1, 2, 3]
}
}
  • 这里的addNumbers函数可以接收List<Integer>, List<Number>, List<Object>.

实际应用

封装统一的 API 响应

在开发 RESTful API 时,我们通常希望返回统一的 JSON 结构,例如:
{ “success”: true, “code”: 200, “message”: “操作成功”, “data”: … }
这里的 data 字段内容是动态变化的,可能是单个用户信息,也可能是商品列表。这时可以使用范型响应类来解决:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
public class ApiResponse<T> {
private boolean success;
private int code;
private String message;
private T data; // data 的类型由 T 决定

// 构造函数、Getter、Setter...

public static <T> ApiResponse<T> success(T data) {
// ... 返回一个表示成功的 ApiResponse 实例
}
}

// 在 Controller 中的应用
@GetMapping("/users/{id}")
public ApiResponse<UserDTO> getUserById(@PathVariable Long id) {
UserDTO user = userService.findUserById(id);
return ApiResponse.success(user);
}

@GetMapping("/products")
public ApiResponse<List<ProductDTO>> listProducts() {
List<ProductDTO> products = productService.findAll();
return ApiResponse.success(products);
}

创建通用的服务层/数据访问层

在典型的分层架构中,许多实体(如 User, Product, Order)的增删改查逻辑非常相似。为每个实体都写一套重复的 DAO/Repository 和 Service 是非常繁琐的。这时,可以利用泛型来抽象出一个通用的基类或接口。以下是一个用MyBatis的例子:

创建通用的 BaseMapper 接口:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
// BaseMapper.java
// 这个接口不需要 @Mapper 注解,因为它不会被直接实例化
public interface BaseMapper<T, ID> {

/**
* 根据ID查找实体
* @param id 主键
* @return 实体对象
*/
T findById(ID id);

/**
* 插入一个实体
* @param entity 实体对象
* @return 影响的行数
*/
int insert(T entity);

/**
* 更新一个实体
* @param entity 实体对象
* @return 影响的行数
*/
int update(T entity);

/**
* 根据ID删除
* @param id 主键
* @return 影响的行数
*/
int deleteById(ID id);
}
  • 这个接口定义了一个契约:任何实现了 BaseMapper 的子接口,都将拥有这四个基本的 CRUD 方法。T 和 ID 此时是占位符。

创建具体的业务 Mapper 接口:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// UserMapper.java
@Mapper
public interface UserMapper extends BaseMapper<User, Long> {
// 继承了 findById(Long), insert(User), update(User), deleteById(Long)
// 只需在这里添加 User 特有的方法
User findByUsername(String username);
}

// ProductMapper.java
@Mapper
public interface ProductMapper extends BaseMapper<Product, Long> {
// 同理
List<Product> findByStatus(int status);
}

编写xml语句:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.example.mapper.UserMapper">

<select id="findById" resultType="com.example.model.User">
SELECT * FROM users WHERE id = #{id}
</select>

<insert id="insert" parameterType="com.example.model.User" useGeneratedKeys="true" keyProperty="id">
INSERT INTO users (username, password) VALUES (#{username}, #{password})
</insert>

<select id="findByUsername" resultType="com.example.model.User">
SELECT * FROM users WHERE username = #{username}
</select>

</mapper>

创建通用的 BaseService 抽象类:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
// BaseService.java
// 这是一个抽象类,不能被直接实例化
public abstract class BaseService<T, ID> {

// 使用 protected,子类也可以访问
protected BaseMapper<T, ID> mapper;

// 通过构造函数,强制子类传入具体的 Mapper 实例
public BaseService(BaseMapper<T, ID> mapper) {
this.mapper = mapper;
}

// 通用的业务逻辑
public T findById(ID id) {
return mapper.findById(id);
}

public int save(T entity) {
// 其他逻辑...
return mapper.insert(entity);
}
}
  • 抽象类:它依赖一个不确定的 BaseMapper,它本身不能独立工作,必须由一个具体的子类来提供具体的 Mapper。
  • 构造函数注入:我们不能直接 @Autowired BaseMapper,因为 Spring 不知道该注入 UserMapper 还是 ProductMapper。所以,我们要求子类在创建时,必须通过构造函数把它自己对应的具体 Mapper 传给父类。

创建具体的业务 Service 类:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
// UserService.java
@Service
public class UserService extends BaseService<User, Long> {

// Spring 会自动找到 UserMapper 的实例并注入到这个构造函数中
@Autowired
public UserService(UserMapper userMapper) {
// 调用父类的构造函数,将具体的 userMapper 传递进去
// 这样,父类 BaseService 中的 mapper 字段就持有了 UserMapper 的引用
super(userMapper);
}
// UserService继承了BaseService的基础的CRUD逻辑

// 在这里编写 User 特有的业务逻辑
public User login(String username, String password) {
User user = ((UserMapper) this.mapper).findByUsername(username); // 需要强转来调用子类特有方法
if (user != null && user.getPassword().equals(password)) {
return user;
}
return null;
}
}

Java基础 - 泛型
https://thiefcat.github.io/2025/07/27/JAVA/generics/
Author
小贼猫
Posted on
July 27, 2025
Licensed under