下面用“从入门到进阶”的顺序,把 **Java 泛型容器(Generic Collections)** 彻底拆开讲一遍。读完你能回答以下问题:
- 为什么需要泛型?
- 背后擦除机制到底做了什么?
- 怎样写自己的泛型容器?
- 常见通配符 `? extends` / `? super` 到底怎么用?
- 性能、反射、序列化等实战陷阱有哪些?
---
### 1. 没有泛型之前(Java 1.4 及以前)
```java
List list = new ArrayList(); // 只能存 Object
list.add("hello");
String s = (String) list.get(0); // 强制转型 + 运行时才能发现错误
```
痛点
- 代码里到处是强制转型。
- 运行时才抛 `ClassCastException`。
---
### 2. 泛型登场(Java 5)
```java
List<String> list = new ArrayList<>(); // 类型参数 <String>
list.add("hello");
String s = list.get(0); // 编译器已保证安全,无需强转
```
编译器在 **编译期** 帮你完成类型检查;运行时 **类型擦除**(type erasure)将 `List<String>` 还原成裸 `List`,里面仍然是 `Object`。
---
### 3. 类型擦除内幕(面试高频)
- 擦除到“上界”
- `List<T>` → `List<Object>`
- `List<T extends Number>` → `List<Number>`
- 插入桥方法(bridge method)保持多态
- 编译器会生成 `public Object get(int)` 桥接 `public T get(int)`
- 不能出现基本类型作为参数
- `List<int>` ❌ → `List<Integer>` ✅
- 不能通过反射拿到泛型实参(运行时没有),但 `Field.getGenericType()` 可以拿到 **声明时** 的信息。
---
### 4. 自己写一个泛型容器(最小可用版)
```java
public class MyStack<E> { // E = Element
private Object[] elements;
private int top = 0;
public MyStack(int capacity) {
elements = new Object[capacity]; // 不能直接 new E[]
}
public void push(E e) {
elements[top++] = e;
}
@SuppressWarnings("unchecked")
public E pop() {
return (E) elements[--top];
}
}
```
要点
- 不能 `new E[]`,只能用 `Object[]` 再强转。
- `@SuppressWarnings("unchecked")` 告诉编译器“我保证安全”。
---
### 5. 泛型方法 & 静态工具
```java
public static <T extends Comparable<T>> T max(T a, T b) {
return a.compareTo(b) > 0 ? a : b;
}
```
调用
```java
Integer m = Util.max(1, 3); // 自动推断 T=Integer
```
---
### 6. 通配符:PECS 原则(Producer-Extends, Consumer-Super)
| 通配符 | 读 | 写 | 场景 |
|--------|----|----|------|
| `List<? extends Fruit>` | 能读,返回 `Fruit` 或其子类 | 不能写(除了 `null`) | **生产者** |
| `List<? super Apple>` | 能读,返回 `Object` | 能写 `Apple` 及其子类 | **消费者** |
| `List<?>` 无界 | 只能读 `Object` | 不能写(除了 `null`) | 只读 |
示例
```java
// 拷贝 src → dest,dest 消费数据 → 用 super
public static <T> void copy(List<? super T> dest, List<? extends T> src) {
for (T t : src) dest.add(t);
}
```
---
### 7. 常见实现 & 线程安全版本
| 接口 | 主要实现类 | 线程安全版 | 备注 |
|------|------------|------------|------|
| `List<E>` | `ArrayList`, `LinkedList` | `CopyOnWriteArrayList`, `Collections.synchronizedList` | 随机访问 vs 插入删除 |
| `Set<E>` | `HashSet`, `LinkedHashSet`, `TreeSet` | `CopyOnWriteArraySet`, `ConcurrentSkipListSet` | 去重、排序 |
| `Map<K,V>` | `HashMap`, `LinkedHashMap`, `TreeMap` | `ConcurrentHashMap`, `ConcurrentSkipListMap` | key 唯一、排序 |
---
### 8. 高级话题
#### 8.1 类型令牌绕过擦除(TypeToken)
```java
Type listOfString = new TypeToken<List<String>>(){}.getType();
List<String> list = new Gson().fromJson(json, listOfString);
```
利用匿名子类在编译期把 `List<String>` 存进 `.class` 常量池,运行时再读出来。
#### 8.2 可变参数 + 泛型警告
```java
public static <T> void addAll(Collection<T> c, T... elements) {
Collections.addAll(c, elements);
}
```
编译器会提示 “Possible heap pollution”——本质上是 `T[]` 与 `Object[]` 协变带来的隐患。
#### 8.3 序列化陷阱
- 擦除后只有 `List<Object>`,序列化框架(如 Jackson)需借助 `TypeReference` 或 `TypeToken` 才能正确反序列化。
---
### 9. 速查口诀
1. **声明**:类/接口后加 `<T>`,方法返回值前加 `<T>`。
2. **擦除**:运行时裸 raw 类型,无实参信息。
3. **通配符**:往里写用 `super`,往外读用 `extends`。
4. **不能**:实例化 `new T()`、`new T[]`、`T.class`。
5. **可变参数**:`T...` 会警告,谨慎使用。
---
### 10. 小结
- **泛型容器 = 容器 + 编译期类型检查 + 运行时擦除**。
- 90% 的场景只需要 `<E>`、`<K,V>` 以及 `<? extends / super>` 三种语法糖;剩下 10% 的坑来自反射、序列化、数组协变等深层机制。
- 面试常问:擦除原理、PECS、为什么不能 `new T[]`,如何绕过。
把上面的代码片段复制到 IDE 里跑一遍,基本就能把泛型容器吃透了。
- 环境配置
- window怎么配置java环境变量?
- Java基础语法
- 数据类型
- Java中的数据类型
- Java中的泛型容器
- Java中的JSONObject
- Java高级特性
- Maven
- jib-maven-plugin
- 什么是Spring Boot 的 parent pom?
- maven中各个生命周期的含义
- Spring Boot
- maven与spring boot 的关系
- Java中的连接池
- Spring JDBC
- Spring JDBC的概念
- JdbcTemplate常用的方法
- Spring中Bean的概念
- Spring中的抽象,通俗解释一下
- Spring中的事物
- Spring中的事物,通俗解释一下
- Spring中的事物抽象,常见的有哪些,列举一下
- Spring中常用的事物场景有哪些,列举一下
- Spring事务管理有哪些注解?
- Spring中使用事物处理订单的案例,列举说明一下
- Spring中声明式事务、分布式事务以及编程式事务的区别,列举一下
- 配置文件
- application-properties配置文件
- Spring Boot 的启动
- spring boot项目如何启动?
- 列举一下Spring Boot的启动过程
- SpringApplication.run方法
- Spring Boot 启动时有哪些接口?
- CommandLineRunner
- Spring Boot 的常用注解
- 系统注解
- 表格:系统注解
- @Override
- @Deprecated
- @SuppressWarnnings
- 使用在类名上的注解
- 表格:使用在类名上的注解
- @RestController
- @Controller
- @Service
- @Repository
- @Component
- @Configuration
- @Resource
- @Autowired
- @RequestMapping
- @GetMapping
- @PostMapping
- @Transactional
- @Qualifier
- 使用在方法上的注解
- 表格:使用在方法上的注解
- @RequestBody
- @PathVariable
- @Bean
- @ResponseBody
- @PreAuthorize
- 其他常用注解
- 表格:其他常用注解
- @EnableAutoConfiguration
- @SpringBootApplication
- @EnableScheduling
- @EnableAsync
- @ComponentScan
- @Aspec
- @ControllerAdvice
- @ExceptionHandler
- @Value
- @ConfigurationProperties
- @EnableConfigurationProperties
- @MapperScan
- @ApiOperation
- Validator验证的常用注解
- spring IoC容器
- Spring IoC容器依赖注入实现方式
- MyBatis
- paginationInterceptor
- @TableName
- @TableId
- @Param
- UrlBasedCorsConfigurationSource
- Lombok
- @Data
- @Slf4j
- @EqualsAndHashCode
- @Accessors
- 支付系统
- 1. 初始化mysql数据库流程
- 2. 初始化redis数据库的流程
- 3. 初始化rabbitmq服务
- 环球置业
- 1.模块目录结构分析
- 2. DTO(数据传输层)的核心作用
- 3. VO(视图对象层)
- 4. VO(视图对象层)和 DTO 数据传输层 的区别
