1943 字
10 分钟
Java泛型的设计和使用
泛型的核心思想是参数化类型,即将类型作为参数传递。这样可以创建能够处理多种数据类型的类、接口和方法,同时保持类型安全。
1. 基本语法
1️⃣ 泛型类
public class Box<T> { private T content;
public void set(T content) { this.content = content; }
public T get() { return content; }}
// 使用Box<String> stringBox = new Box<>();stringBox.set("Hello");String value = stringBox.get();
2️⃣ 泛型接口
public interface Comparable<T> { int compareTo(T other);}
public class Person implements Comparable<Person> { private String name;
@Override public int compareTo(Person other) { return this.name.compareTo(other.name); }}
3️⃣ 泛型方法
public class Utility { public static <T> void swap(T[] array, int i, int j) { T temp = array[i]; array[i] = array[j]; array[j] = temp; }}
// 使用String[] names = {"Alice", "Bob", "Charlie"};Utility.swap(names, 0, 2);
2. 泛型有什么用?
1️⃣类型安全
// 没有泛型的情况 - 容易出错List list = new ArrayList();list.add("Hello");list.add(123);String str = (String) list.get(1); // 运行时ClassCastException!
// 使用泛型 - 编译时检查List<String> stringList = new ArrayList<>();stringList.add("Hello");// stringList.add(123); // 编译错误!String str = stringList.get(0); // 不需要强制转换
2️⃣消除强制类型转换
// 泛型前Map map = new HashMap();map.put("name", "张三");String name = (String) map.get("name"); // 需要强制转换
// 泛型后Map<String, String> userMap = new HashMap<>();userMap.put("name", "张三");String name = userMap.get("name"); // 直接获取,无需转换
3️⃣ 更好的代码重用性
// 一个泛型类可以处理多种类型Box<Integer> intBox = new Box<>();Box<String> stringBox = new Box<>();Box<Person> personBox = new Box<>();
3. 通配符
为什么要把通配符设计成‘上下无’这三种?有什么实际意义吗? 答:三种通配符的设计其实解决了泛型中的一个核心问题:如何在保证类型安全的前提下,实现灵活的类型关系。
3.1 通配符的设计(核心问题:泛型的不变性)
首先要理解一个重要概念:Java的泛型默认是不变的(invariant):
List<Object> objectList = new ArrayList<String>(); // 编译错误!
// 即使 String 是 Object 的子类,// List<String> 也不是 List<Object> 的子类
// 假如java中容许下面这样赋值// List<String> strList = new ArrayList<>();// List<Object> objList = strList;// `strList` 指向的容器里本该只放 `String`,结果里面混进了 `Integer`,// 一旦取出就会导致ClassCastException。这就破坏了泛型的编译期类型安全保证。// objList.add(123);
1️⃣ 上界通配符<? extends T
-解决“协变”需求
场景:我想读取数据,但我不关心具体是哪个类
// 没有通配符的问题public static double sumNumbers(List<Number> numbers) { return numbers.stream().mapToDouble(Number::doubleValue).sum();}
// 这样调用会出错:List<Integer> integers = Arrays.asList(1, 2, 3);// sumNumbers(integers); // 编译错误!List<Integer> 不是 List<Number>
// 使用上界通配符解决:public static double sumNumbers(List<? extends Number> numbers) { return numbers.stream().mapToDouble(Number::doubleValue).sum();}
// 现在可以接受Number的任何子类:List<Integer> integers = Arrays.asList(1, 2, 3);List<Double> doubles = Arrays.asList(1.1, 2.2, 3.3);List<BigDecimal> decimals = Arrays.asList(new BigDecimal("1"), new BigDecimal("2"));
sumNumbers(integers); // ✓ 正常工作sumNumbers(doubles); // ✓ 正常工作sumNumbers(decimals); // ✓ 正常工作
为什么只能读不能写?
List<? extends Number> numbers = new ArrayList<Integer>();
// 读取是安全的 - 我们知道取出的一定是Number或其子类Number num = numbers.get(0); // ✓ 安全
// 写入是危险的 - 我们不知道具体是什么子类型// numbers.add(3.14); // ✗ 如果实际是List<Integer>,加入Double就出错了// numbers.add(new BigDecimal("1")); // ✗ 同样的问题
为什么用
? extends Number
就能接受?
这是一个“元素类型是 Number 或其子类”的 List,但具体是哪种子类,编译器不知道。于是你可以安全地 读取 元素(它们至少是 Number
),但不能随便 写入。因为写入时,编译器没法确定你放的类型和 List 实际持有的类型是否兼容。
2️⃣ 下界通配符 ? super T
- 解决”逆变”需求
场景:我想写入数据,但希望能写入到更通用的容器中
// 添加整数到容器中public static void addIntegers(List<? super Integer> list) { list.add(1); list.add(2); list.add(3); // 我们知道容器至少可以装Integer,所以添加Integer是安全的}
// 可以传入Integer的父类型容器:List<Integer> intList = new ArrayList<>();List<Number> numberList = new ArrayList<>();List<Object> objectList = new ArrayList<>();
addIntegers(intList); // ✓ 直接匹配addIntegers(numberList); // ✓ Number是Integer的父类addIntegers(objectList); // ✓ Object是Integer的父类
// 但不能传入子类或兄弟类:// List<Double> doubleList = new ArrayList<>();// addIntegers(doubleList); // ✗ Double不是Integer的父类
为什么能写但读取受限?
List<? super Integer> list = new ArrayList<Number>();
// 写入是安全的 - 我们知道容器至少能装Integerlist.add(42); // ✓ 安全list.add(new Integer(100)); // ✓ 安全
// 读取受限 - 我们不知道容器的确切类型Object obj = list.get(0); // ✓ 只能作为Object读取// Integer i = list.get(0); // ✗ 不安全,容器可能是List<Object>
3️⃣ 无界通配符?
- 解决”我不关心类型”的需求
场景:只使用容器的通用操作,不涉及具体元素
// 比较两个集合的大小public static int compareSize(Collection<?> c1, Collection<?> c2) { return Integer.compare(c1.size(), c2.size()); // 不需要知道具体类型,只用Collection的基本方法}
// 清空任何类型的集合public static void clearCollection(Collection<?> collection) { collection.clear(); // 清空操作与具体类型无关}
3.2 PECS原则:Producer Extends, Consumer Super
public class Collections {
// Producer场景:从source读取数据,所以用extends public static <T> void copy(List<? super T> dest, List<? extends T> src) { for (int i = 0; i < src.size(); i++) { dest.set(i, src.get(i)); } }
// src是生产者(Producer) - 提供数据,用extends // dest是消费者(Consumer) - 接收数据,用super}
// 实际使用:List<Number> numbers = new ArrayList<>();List<Integer> integers = Arrays.asList(1, 2, 3);
Collections.copy(numbers, integers); // ✓ 将Integer复制到Number容器中
3.3 实际应用:集合框架
// Java集合框架中的实际例子
// 1. addAll方法 - Consumer场景,用superpublic boolean addAll(Collection<? extends E> c);
// 2. Comparator接口 - Producer场景,用extendspublic static <T> void sort(List<T> list, Comparator<? super T> c);
// 3. 通用工具方法 - 不关心类型,用无界public static void shuffle(List<?> list);public static void reverse(List<?> list);
4. 泛型在实际开发中的应用
1️⃣ 集合框架
// 用户列表List<User> users = new ArrayList<>();users.add(new User("张三", 25));users.add(new User("李四", 30));
// 配置映射Map<String, String> config = new HashMap<>();config.put("database.url", "jdbc:mysql://localhost:3306/test");config.put("database.username", "root");
// 响应结果Set<Long> processedIds = new HashSet<>();processedIds.add(1001L);processedIds.add(1002L);
2️⃣ API响应封装
// 通用响应类public class ApiResponse<T> { private int code; private String message; private T data;
public static <T> ApiResponse<T> success(T data) { ApiResponse<T> response = new ApiResponse<>(); response.code = 200; response.message = "成功"; response.data = data; return response; }
public static <T> ApiResponse<T> error(String message) { ApiResponse<T> response = new ApiResponse<>(); response.code = 500; response.message = message; return response; }
// getter/setter...}
// 使用示例@RestControllerpublic class UserController {
@GetMapping("/user/{id}") public ApiResponse<User> getUser(@PathVariable Long id) { User user = userService.findById(id); return ApiResponse.success(user); }
@GetMapping("/users") public ApiResponse<List<User>> getUsers() { List<User> users = userService.findAll(); return ApiResponse.success(users); }}
3️⃣ DAO层泛型基类
// 通用DAO接口public interface BaseDao<T, ID> { T save(T entity); T findById(ID id); List<T> findAll(); void deleteById(ID id); List<T> findByExample(T example);}
// 具体实现@Repositorypublic class UserDao implements BaseDao<User, Long> {
@Override public User save(User user) { // 保存逻辑 return entityManager.merge(user); }
@Override public User findById(Long id) { return entityManager.find(User.class, id); }
// 其他方法实现...}
// Service层使用@Servicepublic class UserService { @Autowired private UserDao userDao;
public User createUser(User user) { return userDao.save(user); }}
5. 实际业务场景
1️⃣ 分页查询结果
public class PageResult<T> { private List<T> data; // 当前页数据 private long total; // 总记录数 private int page; // 当前页码 private int size; // 每页大小 private int totalPages; // 总页数
public PageResult(List<T> data, long total, int page, int size) { this.data = data; this.total = total; this.page = page; this.size = size; this.totalPages = (int) Math.ceil((double) total / size); }
// getter/setter...}
// 使用示例@Servicepublic class ProductService {
public PageResult<Product> getProducts(int page, int size) { List<Product> products = productDao.findByPage(page, size); long total = productDao.count(); return new PageResult<>(products, total, page, size); }}
2️⃣ Builder模式与泛型
public class QueryBuilder<T> { private StringBuilder sql = new StringBuilder(); private List<Object> parameters = new ArrayList<>();
public QueryBuilder<T> select(String fields) { sql.append("SELECT ").append(fields); return this; }
public QueryBuilder<T> from(String table) { sql.append(" FROM ").append(table); return this; }
public QueryBuilder<T> where(String condition, Object value) { sql.append(" WHERE ").append(condition); parameters.add(value); return this; }
public QueryBuilder<T> and(String condition, Object value) { sql.append(" AND ").append(condition); parameters.add(value); return this; }
public String build() { return sql.toString(); }
public List<Object> getParameters() { return parameters; }}
// 使用示例QueryBuilder<User> userQuery = new QueryBuilder<User>() .select("*") .from("users") .where("age > ?", 18) .and("status = ?", "ACTIVE");
String sql = userQuery.build();List<Object> params = userQuery.getParameters();