Wrayの知识库 Wrayの知识库
首页
  • Java 基础
  • Java 集合
  • Java 并发
  • Java IO
  • JVM
  • Spring Framework
  • Spring Boot
  • Spring Cloud
  • Spring Security
  • MySQL
  • Redis
  • 计算机基础
  • 操作系统原理
  • Linux
  • MacOS
  • Windows
  • 系统工程与研究专题
  • AI 基础
  • 大模型基础
  • Prompt 工程
  • RAG 检索增强生成
  • Agent 智能体
  • AI 应用开发
  • AI 工程化
  • AI 安全与治理
  • AI 面试与设计题
  • 纸质书
  • 电子书
  • 学习课程
疑难杂症
GitHub (opens new window)
首页
  • Java 基础
  • Java 集合
  • Java 并发
  • Java IO
  • JVM
  • Spring Framework
  • Spring Boot
  • Spring Cloud
  • Spring Security
  • MySQL
  • Redis
  • 计算机基础
  • 操作系统原理
  • Linux
  • MacOS
  • Windows
  • 系统工程与研究专题
  • AI 基础
  • 大模型基础
  • Prompt 工程
  • RAG 检索增强生成
  • Agent 智能体
  • AI 应用开发
  • AI 工程化
  • AI 安全与治理
  • AI 面试与设计题
  • 纸质书
  • 电子书
  • 学习课程
疑难杂症
GitHub (opens new window)
  • Java章节编写规范
  • Java基础

    • Java概述
    • Java语法
    • 面向对象编程
    • Java数组
    • String字符串
    • 异常处理
    • 基本类型与包装类
    • Object类与常用方法
    • 泛型
      • 1. 泛型类
      • 2. 泛型接口
      • 3. 泛型方法
      • 4. 类型擦除
      • 5. 原始类型 Raw Type
      • 6. 泛型通配符
        • 6.1 ?
        • 6.2 上界通配符 ? extends
        • 6.3 下界通配符 ? super
      • 7. PECS 原则
      • 8. 泛型边界
      • 9. 泛型数组限制
      • 10. 泛型不变性
      • 11. 桥接方法
      • 12. 泛型与反射
      • Tips 快问快答
    • 枚举与注解
    • 反射
    • 值传递与引用
    • Lambda表达式与函数式接口
    • Stream API
    • Optional与空值处理
    • 日期时间API
    • Java现代语法特性
  • Java集合

  • Java并发

  • Java IO

  • JVM

目录

泛型

泛型用于把类型作为参数传给类、接口或方法。它让代码在保持复用性的同时获得编译期类型检查。

没有泛型时,集合只能保存 Object:

List list = new ArrayList();
list.add("Java");

String value = (String) list.get(0);

有泛型后:

List<String> list = new ArrayList<>();
list.add("Java");

String value = list.get(0);

泛型的核心收益:

  • 编译期发现类型错误。
  • 减少强制类型转换。
  • 提升 API 表达能力。
  • 让容器、工具方法、框架扩展点更通用。

# 1. 泛型类

泛型类把类型参数放在类名后。

public class Box<T> {
    private T value;

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

    public T get() {
        return value;
    }
}

使用:

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

T 是类型参数,创建对象时由具体类型替换。

常见类型参数命名:

名称 含义
T Type
E Element
K Key
V Value
R Result
? 未知类型

# 2. 泛型接口

public interface Repository<T, ID> {
    T findById(ID id);

    void save(T entity);
}

实现时指定类型:

public class UserRepository implements Repository<User, Long> {
    @Override
    public User findById(Long id) {
        return null;
    }

    @Override
    public void save(User entity) {
    }
}

也可以让实现类继续保持泛型:

public class MemoryRepository<T, ID> implements Repository<T, ID> {
    public T findById(ID id) {
        return null;
    }

    public void save(T entity) {
    }
}

# 3. 泛型方法

泛型方法把类型参数放在返回值前。

public static <T> T first(List<T> list) {
    if (list == null || list.isEmpty()) {
        return null;
    }
    return list.get(0);
}

调用:

String first = first(List.of("A", "B"));
Integer number = first(List.of(1, 2));

多个类型参数:

public static <K, V> Map<K, V> mapOf(K key, V value) {
    Map<K, V> map = new HashMap<>();
    map.put(key, value);
    return map;
}

泛型方法适合把类型关系绑定在一次方法调用中。

# 4. 类型擦除

Java 泛型主要在编译期生效,运行期会进行类型擦除。

List<String> names = new ArrayList<>();
List<Integer> numbers = new ArrayList<>();

System.out.println(names.getClass() == numbers.getClass()); // true

运行时它们都是 ArrayList。

擦除过程简化理解:

源码
List&lt;String> names
   │ 编译期类型检查
   ▼
字节码
List names
   │ 必要位置插入强转
   ▼
运行期
普通 ArrayList 对象

示例:

String value = names.get(0);

编译后大致类似:

String value = (String) names.get(0);

类型擦除带来的限制:

  • 不能直接创建泛型数组。
  • 运行期不能用 instanceof List<String>。
  • 不能获取 T.class。
  • 泛型重载可能冲突。

# 5. 原始类型 Raw Type

不指定泛型参数就是原始类型。

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

问题:

List<String> names = list;
String name = names.get(1); // ClassCastException

原始类型绕过了编译期检查,把错误推迟到运行期。

开发中除非兼容老 API,否则不要使用原始类型。

# 6. 泛型通配符

# 6.1 ?

? 表示未知类型。

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

List<?> 可以接收任意元素类型的列表,但不能随意添加具体元素。

List<?> list = new ArrayList<String>();
// list.add("x"); // 编译失败
list.add(null); // 可以

因为编译器不知道真实元素类型是什么。

# 6.2 上界通配符 ? extends

? extends Number 表示某种 Number 子类型。

public double sum(List<? extends Number> numbers) {
    double total = 0;
    for (Number number : numbers) {
        total += number.doubleValue();
    }
    return total;
}

可以传入:

sum(List.of(1, 2, 3));
sum(List.of(1.1, 2.2));

但不能添加具体数字:

List<? extends Number> numbers = new ArrayList<Integer>();
// numbers.add(1); // 编译失败

原因:

List&lt;? extends Number>
可能是 List&lt;Integer>
可能是 List&lt;Double>
可能是 List&lt;BigDecimal>

编译器不知道实际是哪一种,所以不允许写入具体 Number 子类。

它适合读取,因此常叫 Producer。

# 6.3 下界通配符 ? super

? super Integer 表示某种 Integer 父类型。

public void addNumbers(List<? super Integer> list) {
    list.add(1);
    list.add(2);
}

可以传入:

List<Integer> integers = new ArrayList<>();
List<Number> numbers = new ArrayList<>();
List<Object> objects = new ArrayList<>();

addNumbers(integers);
addNumbers(numbers);
addNumbers(objects);

读取时只能安全地当作 Object:

Object value = list.get(0);

它适合写入,因此常叫 Consumer。

# 7. PECS 原则

PECS:Producer Extends, Consumer Super。

场景 写法 理由
从集合中读取 T ? extends T 生产 T
向集合中写入 T ? super T 消费 T

示例:

public static <T> void copy(List<? extends T> src, List<? super T> dest) {
    for (T item : src) {
        dest.add(item);
    }
}

结构:

src:  List&lt;? extends T>  负责生产元素
          │
          ▼
          T
          │
          ▼
dest: List&lt;? super T>    负责接收元素

这是理解 Collections.copy 等 API 的关键。

# 8. 泛型边界

可以限制类型参数上界:

public class NumberBox<T extends Number> {
    private T value;

    public double doubleValue() {
        return value.doubleValue();
    }
}

多个边界:

public static <T extends Number & Comparable<T>> T max(T a, T b) {
    return a.compareTo(b) >= 0 ? a : b;
}

规则:如果有类边界,类必须放在第一个,接口放后面。

<T extends SomeClass & InterfaceA & InterfaceB>

# 9. 泛型数组限制

不能直接创建泛型数组:

// T[] array = new T[10]; // 编译失败

也不能创建具体泛型数组:

// List<String>[] lists = new List<String>[10]; // 编译失败

原因是数组在运行期知道元素类型,而泛型在运行期被擦除,两者类型机制不匹配。

数组是协变且运行期检查:

Object[] array = new String[1];
array[0] = 123; // ArrayStoreException

泛型是不变且编译期检查:

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

# 10. 泛型不变性

即使 Integer 是 Number 的子类,List<Integer> 也不是 List<Number> 的子类。

List<Integer> integers = new ArrayList<>();
// List<Number> numbers = integers; // 编译失败

如果允许,会出现问题:

List<Integer> integers = new ArrayList<>();
List<Number> numbers = integers;
numbers.add(1.5);
Integer value = integers.get(0); // 类型污染

所以 Java 泛型默认不变。需要灵活接收时使用通配符。

# 11. 桥接方法

类型擦除可能导致编译器生成桥接方法,保证多态仍然正确。

class Parent<T> {
    T get() {
        return null;
    }
}

class Child extends Parent<String> {
    @Override
    String get() {
        return "x";
    }
}

擦除后父类方法类似 Object get(),子类方法是 String get()。编译器会生成桥接方法:

Child
├─ String get()   真实方法
└─ Object get()   bridge,内部调用 String get()

这也是反射查看方法时可能看到 bridge 方法的原因。

# 12. 泛型与反射

虽然泛型会擦除,但类、字段、方法签名中的泛型信息有些会保留在元数据中。

Field field = UserService.class.getDeclaredField("users");
Type type = field.getGenericType();

框架经常利用这些信息解析:

  • Controller 参数类型。
  • JSON 序列化目标类型。
  • ORM 实体字段类型。
  • 依赖注入候选类型。

但局部变量的泛型信息通常不能在运行期直接获取。

# Tips 快问快答

Q:泛型解决什么问题? A:让类型错误尽量在编译期暴露,并减少强制类型转换。

Q:Java 泛型是真泛型还是伪泛型? A:Java 泛型基于类型擦除,运行期大多看不到具体类型参数。

Q:为什么不能 new T()? A:类型擦除后运行期不知道 T 的具体类,也不知道该调用哪个构造器。

Q:为什么不能 new T[10]? A:数组需要运行期元素类型,泛型类型参数运行期被擦除。

Q:List<Integer> 是 List<Number> 的子类吗? A:不是。泛型默认不变,避免类型污染。

Q:什么时候用 ? extends T? A:主要从集合读取数据时使用。

Q:什么时候用 ? super T? A:主要向集合写入数据时使用。

Q:PECS 怎么记? A:生产者用 extends,消费者用 super。

Q:Raw Type 有什么问题? A:绕过泛型检查,可能把类型错误推迟到运行期。

Q:为什么反射里会看到 bridge 方法? A:编译器为了在类型擦除后保持泛型多态语义,会生成桥接方法。

上次更新: 2026/06/24, 16:28:28
Object类与常用方法
枚举与注解

← Object类与常用方法 枚举与注解→

Copyright © 2023-2026 Wray | 鄂ICP备2024050235号-1
  • 跟随系统
  • 浅色模式
  • 深色模式
  • 阅读模式