9、Java 泛型原理与使用详解

【投稿赢 iPhone 17】「我的第一个开源项目」故事征集:用代码换C位出道! 10w+人浏览 1.6k人参与

开篇概览:泛型的核心价值

泛型(Generics) 是 Java 5(JDK 1.5)引入的重要特性,其核心目标是:

  • 编译期类型安全检查:避免运行时 ClassCastException
  • 消除强制类型转换:代码更简洁、可读;
  • 提升代码复用性:一套逻辑适配多种数据类型。

泛型的本质是参数化类型(Parameterized Types)——将类型作为参数传递给类、接口或方法,从而在编译阶段确保类型一致性。

本章将深入讲解:

  1. 泛型基本语法与原理(类型擦除);
  2. 泛型类、泛型方法、泛型接口
  3. 类型通配符(?<? extends T><? super T>
  4. 边界限定与最佳实践

一、泛型基本原理:类型擦除(Type Erasure)

1.1 什么是类型擦除?

  • Java 的泛型是编译期特性,运行时不存在泛型信息
  • 编译器在编译时擦除泛型类型参数,替换为上限类型(通常是 Object);
  • 通过桥接方法(Bridge Methods)强制类型转换保证类型安全。
示例:泛型编译后等价代码
// 源码:泛型类
public class Box<T> {
    private T value;
    public void set(T value) { this.value = value; }
    public T get() { return value; }
}

// 编译后(等价于)
public class Box {
    private Object value;
    public void set(Object value) { this.value = value; }
    public Object get() { return value; }
}

优势:兼容 Java 5 之前的字节码;
局限:运行时无法获取泛型实际类型(如 T.class 非法)。


二、泛型类(Generic Class)

2.1 定义与使用

  • 在类名后使用 <T> 声明类型参数;
  • T类型变量(可为任意标识符,常用 T, E, K, V)。
示例:通用容器类
// 中文注释:定义一个泛型盒子类,可存储任意类型的数据
public class Box<T> {
    // 私有字段,类型为泛型 T
    private T content;

    // 设置内容(参数类型为 T)
    public void setContent(T content) {
        this.content = content;
    }

    // 获取内容(返回类型为 T)
    public T getContent() {
        return content;
    }

    // 重写 toString 方法,便于打印
    @Override
    public String toString() {
        return "Box 内容: " + content;
    }
}

// 测试泛型类
public class GenericClassDemo {
    public static void main(String[] args) {
        // 创建存储字符串的盒子
        Box<String> stringBox = new Box<>();
        stringBox.setContent("Hello 泛型");
        System.out.println(stringBox); // Box 内容: Hello 泛型

        // 创建存储整数的盒子
        Box<Integer> intBox = new Box<>();
        intBox.setContent(123);
        System.out.println(intBox); // Box 内容: 123

        // 编译器自动类型检查:以下代码会报错
        // stringBox.setContent(456); // ❌ 编译错误:Integer 不能赋值给 String
    }
}

2.2 多类型参数

// 中文注释:定义一个键值对泛型类(类似简化版 Map)
public class Pair<K, V> {
    private K key;
    private V value;

    public Pair(K key, V value) {
        this.key = key;
        this.value = value;
    }

    public K getKey() { return key; }
    public V getValue() { return value; }

    @Override
    public String toString() {
        return "Pair{" + key + "=" + value + "}";
    }
}

// 使用
Pair<String, Integer> score = new Pair<>("张三", 95);
System.out.println(score); // Pair{张三=95}

三、泛型方法(Generic Method)

3.1 定义与使用

  • 返回类型前声明类型参数 <T>
  • 可独立于类的泛型存在(普通类也可定义泛型方法)。
示例:泛型工具方法
// 中文注释:定义一个包含泛型方法的工具类
public class GenericMethodDemo {
    // 泛型方法:交换数组中两个位置的元素
    public static <T> void swap(T[] array, int i, int j) {
        T temp = array[i];
        array[i] = array[j];
        array[j] = temp;
    }

    // 泛型方法:打印任意类型数组
    public static <T> void printArray(T[] array) {
        for (T item : array) {
            System.out.print(item + " ");
        }
        System.out.println();
    }

    public static void main(String[] args) {
        // 字符串数组
        String[] names = {"张三", "李四", "王五"};
        swap(names, 0, 2);
        printArray(names); // 王五 李四 张三

        // 整数数组
        Integer[] numbers = {1, 2, 3};
        swap(numbers, 0, 1);
        printArray(numbers); // 2 1 3
    }
}

优势:方法逻辑与具体类型解耦,复用性强。


四、泛型接口(Generic Interface)

4.1 定义与实现

  • 接口可声明泛型参数;
  • 实现类可指定具体类型,或保留泛型。
示例:泛型数据处理器
// 中文注释:定义一个泛型数据处理接口
interface DataProcessor<T> {
    // 处理数据并返回结果
    T process(T data);
}

// 实现类:指定具体类型(String)
class StringProcessor implements DataProcessor<String> {
    @Override
    public String process(String data) {
        return data.toUpperCase(); // 转为大写
    }
}

// 实现类:保留泛型(通用处理器)
class IdentityProcessor<T> implements DataProcessor<T> {
    @Override
    public T process(T data) {
        return data; // 原样返回
    }
}

// 测试
public class GenericInterfaceDemo {
    public static void main(String[] args) {
        DataProcessor<String> stringProc = new StringProcessor();
        System.out.println(stringProc.process("hello")); // HELLO

        DataProcessor<Integer> intProc = new IdentityProcessor<>();
        System.out.println(intProc.process(123)); // 123
    }
}

五、类型通配符(Wildcards)

通配符 ? 用于表示未知类型,增强泛型的灵活性。

5.1 无界通配符 <?>

  • 表示“任意类型”;
  • 只能读取,不能写入(除 null 外)。
示例:安全遍历任意类型列表
// 中文注释:打印任意类型 List 的内容(只读)
public static void printList(List<?> list) {
    for (Object item : list) { // 通配符列表只能转为 Object
        System.out.println(item);
    }
    // list.add("new"); // ❌ 编译错误:无法确定列表实际类型
}

public class WildcardDemo {
    public static void main(String[] args) {
        List<String> strList = Arrays.asList("A", "B");
        List<Integer> intList = Arrays.asList(1, 2);

        printList(strList); // 正常输出
        printList(intList); // 正常输出
    }
}

5.2 上界通配符 <? extends T>

  • 表示“T 或 T 的子类型”;
  • PECS 原则Producer-Extends(生产者用 extends);
  • 可读不可写(因编译器不知具体子类型)。
示例:计算数字列表的总和
// 中文注释:计算 Number 及其子类(Integer, Double 等)列表的总和
public static double sumOfList(List<? extends Number> list) {
    double sum = 0.0;
    for (Number num : list) { // 安全:所有元素都是 Number
        sum += num.doubleValue();
    }
    return sum;
}

public class UpperBoundedWildcardDemo {
    public static void main(String[] args) {
        List<Integer> integers = Arrays.asList(1, 2, 3);
        List<Double> doubles = Arrays.asList(1.5, 2.5, 3.5);

        System.out.println("整数和: " + sumOfList(integers));   // 6.0
        System.out.println("小数和: " + sumOfList(doubles));    // 7.5

        // sumOfList(Arrays.asList("A", "B")); // ❌ 编译错误:String 不是 Number 子类
    }
}

5.3 下界通配符 <? super T>

  • 表示“T 或 T 的父类型”;
  • PECS 原则Consumer-Super(消费者用 super);
  • 可写不可读(读取时只能转为 Object)。
示例:将字符串列表复制到任意父类型列表
// 中文注释:将源列表(String)复制到目标列表(String 或其父类,如 Object)
public static void copy(List<String> src, List<? super String> dest) {
    for (String s : src) {
        dest.add(s); // 安全:dest 至少能接受 String
    }
}

public class LowerBoundedWildcardDemo {
    public static void main(String[] args) {
        List<String> src = Arrays.asList("Java", "泛型");
        List<Object> dest = new ArrayList<>();

        copy(src, dest); // 成功:Object 是 String 的父类
        System.out.println(dest); // [Java, 泛型]

        // copy(src, new ArrayList<Integer>()); // ❌ 编译错误:Integer 不是 String 父类
    }
}

六、泛型边界限定(Bounded Type Parameters)

在定义泛型时限制类型参数的范围。

6.1 上界限定 <T extends 类/接口>

  • T 必须是指定类型或其子类
  • 可指定多个接口(用 & 连接),但只能有一个类
示例:限定泛型为可比较类型
// 中文注释:定义一个泛型方法,要求类型实现 Comparable 接口
public static <T extends Comparable<T>> T max(T a, T b) {
    return a.compareTo(b) > 0 ? a : b;
}

// 多重边界:必须是 Number 且实现 Comparable
public static <T extends Number & Comparable<T>> T min(T a, T b) {
    return a.compareTo(b) < 0 ? a : b;
}

public class BoundedTypeDemo {
    public static void main(String[] args) {
        System.out.println(max("Apple", "Banana")); // Banana
        System.out.println(min(3.14, 2.71));        // 2.71

        // max(123, "abc"); // ❌ 编译错误:类型不一致
    }
}

七、泛型最佳实践与注意事项

✅ 推荐做法

  1. 优先使用泛型:避免原始类型(如 List 而非 List<String>);
  2. 遵循 PECS 原则
    • 生产数据(读取)<? extends T>
    • 消费数据(写入)<? super T>
  3. 方法参数尽量用通配符,返回类型避免通配符;
  4. 泛型类中避免创建泛型数组(可用 ArrayList 替代)。

❌ 避免做法

  • 泛型与基本类型混用:需使用包装类(List<int> 非法,应为 List<Integer>);
  • 运行时依赖泛型类型
    // ❌ 非法:无法获取 T 的 Class
    T instance = new T(); 
    Class<T> clazz = T.class;
    
  • 在静态上下文中使用类泛型参数
    public class Box<T> {
        // ❌ 非法:静态方法不能访问类泛型 T
        public static void method(T t) { }
    }
    

八、总结:泛型核心要点

特性说明
类型擦除运行时无泛型信息,编译期检查
泛型类class Box<T> { ... }
泛型方法<T> void method(T t)
泛型接口interface Processor<T> { ... }
无界通配符List<?> — 任意类型,只读
上界通配符List<? extends Number> — 生产者
下界通配符List<? super String> — 消费者
边界限定<T extends Comparable<T>>

📌 核心思想
“泛型是编译器的语法糖,但却是类型安全的守护者。”
合理使用泛型,可让代码更安全、简洁、可维护,是现代 Java 开发的必备技能。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

龙茶清欢

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值