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 );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 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) { Box<String> stringBox = new Box <>(); stringBox.set("Hello, Generics!" ); String content = stringBox.get(); System.out.println(content); 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 ); System.out.println(java.util.Arrays.toString(names)); Integer[] numbers = {1 , 2 , 3 , 4 , 5 }; Integer middleNumber = ArrayUtils.getMiddleElement(numbers); System.out.println(middleNumber); } }
<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> { private String name; private int age; @Override public int compareTo (Student other) { 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 { public static double sumOfList (List<? extends Number> list) { double sum = 0.0 ; for (Number n : list) { sum += n.doubleValue(); } 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 { public static void addNumbers (List<? super Integer> list) { for (int i = 1 ; i <= 3 ; i++) { list.add(i); } } public static void main (String[] args) { List<Integer> intList = new ArrayList <>(); addNumbers(intList); System.out.println(intList); List<Number> numList = new ArrayList <>(); addNumbers(numList); System.out.println(numList); List<Object> objList = new ArrayList <>(); addNumbers(objList); System.out.println(objList); } }
这里的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; public static <T> ApiResponse<T> success (T data) { } }@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 public interface BaseMapper <T, ID> { T findById (ID id) ; int insert (T entity) ; int update (T entity) ; int deleteById (ID id) ; }
这个接口定义了一个契约:任何实现了 BaseMapper 的子接口,都将拥有这四个基本的 CRUD 方法。T 和 ID 此时是占位符。
创建具体的业务 Mapper 接口:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 @Mapper public interface UserMapper extends BaseMapper <User, Long> { User findByUsername (String username) ; }@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 public abstract class BaseService <T, ID> { protected BaseMapper<T, ID> 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 @Service public class UserService extends BaseService <User, Long> { @Autowired public UserService (UserMapper userMapper) { super (userMapper); } 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 ; } }