✔️ 类型安全 · ✔️ 消除强制转换 · ✔️ PECS 原则 —— 让你的代码更通用、更安全


目录

📚 一、知识点速览

🧪 二、实验环境准备

✨ 三、场景一:泛型类 —— 类型安全的容器

🎯 目标

📝 代码(GenericBoxDemo.java)

✅ 运行结果

🧠 核心知识点

🔄 四、场景二:泛型方法 —— 通用的数组元素交换

🎯 目标

📝 代码(GenericMethodDemo.java)

✅ 运行结果

🧠 核心知识点

🔗 五、场景三:泛型接口 + 通配符 —— 键值对容器

🎯 目标

📝 代码(GenericInterfaceDemo.java)

✅ 运行结果

🧠 核心知识点

🔬 六、进阶拔高:类型擦除与通配符原理

1. 类型擦除

2. 通配符的设计意图

3. 无限定通配符 ?

⚠️ 七、常见陷阱与解决方案

📊 八、泛型 vs 非泛型 —— 终极对比

💡 九、课后思考题

📚 十、参考文献

🙏 写在最后


📌 适用人群:Java 初学者、准备面试、编写通用库的开发者
🔥 你将学到:泛型类、泛型方法、泛型接口、通配符 ?、上下界、类型擦除原理


📚 一、知识点速览

知识点 说明 重要度
泛型类 类名后加 <T>,定义类型参数 ⭐⭐⭐⭐⭐
泛型方法 返回值前加 <T>,独立于类的类型参数 ⭐⭐⭐⭐⭐
泛型接口 接口名后加 <K,V>,实现类指定或保留类型参数 ⭐⭐⭐⭐
通配符 ? 表示未知类型,配合 extends / super ⭐⭐⭐⭐⭐
PECS 原则 Producer Extends, Consumer Super ⭐⭐⭐⭐
类型擦除 编译后泛型信息被移除,替换为 Object 或边界 ⭐⭐⭐⭐

🧪 二、实验环境准备

  • JDK 8+

  • VS Code / IntelliJ IDEA

  • 新建项目 GenericBasicDemo,创建三个 Java 文件(分别对应三个场景)


✨ 三、场景一:泛型类 —— 类型安全的容器

🎯 目标

定义一个泛型类 Box<T>,可以存放任意类型的数据,取出时无需强制转换。

📝 代码(GenericBoxDemo.java

java

/**
 * 场景一:泛型类的基本使用
 * 演示类型安全,避免强制转换
 */
public class GenericBoxDemo {
    // 泛型类 Box<T>,T 是类型参数
    static class Box<T> {
        private T content;

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

        public T get() {
            return content;
        }
    }

    public static void main(String[] args) {
        System.out.println("=== 场景一:泛型类 ===");

        // Box 专门装 String
        Box<String> stringBox = new Box<>();
        stringBox.set("Hello Generic");
        String str = stringBox.get(); // 无需强制转换
        System.out.println("String box contains: " + str);

        // Box 专门装 Integer
        Box<Integer> intBox = new Box<>();
        intBox.set(100);
        int num = intBox.get(); // 自动拆箱
        System.out.println("Integer box contains: " + num);
    }
}

✅ 运行结果

text

=== 场景一:泛型类 ===
String box contains: Hello Generic
Integer box contains: 100

📸 截图1GenericBoxDemo.java 完整代码及运行结果截图

🧠 核心知识点

  • Box<T> 中的 T 称为类型参数,使用时替换为具体类型(如 StringInteger)。

  • 编译器会在编译时检查类型一致性,避免将 Integer 放入 Box<String>

  • 取出元素时自动是具体类型,无需强制转换


🔄 四、场景二:泛型方法 —— 通用的数组元素交换

🎯 目标

编写一个泛型方法 swap,可以交换任意类型数组中两个元素的位置。

📝 代码(GenericMethodDemo.java

java

import java.util.Arrays;

/**
 * 场景二:泛型方法
 * 交换数组中两个元素的位置
 */
public class GenericMethodDemo {
    // 泛型方法 <T> 放在返回值之前
    public static <T> void swap(T[] array, int i, int j) {
        T temp = array[i];
        array[i] = array[j];
        array[j] = temp;
    }

    public static void main(String[] args) {
        System.out.println("=== 场景二:泛型方法 ===");

        // 交换 String 数组
        String[] words = {"A", "B", "C"};
        System.out.println("交换前: " + Arrays.toString(words));
        swap(words, 0, 2);
        System.out.println("交换后: " + Arrays.toString(words));

        // 交换 Integer 数组
        Integer[] numbers = {1, 2, 3};
        System.out.println("\n交换前: " + Arrays.toString(numbers));
        swap(numbers, 0, 2);
        System.out.println("交换后: " + Arrays.toString(numbers));
    }
}

✅ 运行结果

text

=== 场景二:泛型方法 ===
交换前: [A, B, C]
交换后: [C, B, A]

交换前: [1, 2, 3]
交换后: [3, 2, 1]

📸 截图3GenericMethodDemo.java 代码及运行结果截图

🧠 核心知识点

  • 泛型方法的类型参数 <T> 放在返回值之前

  • 该方法可以独立于类的类型参数,甚至可以定义在普通类中。

  • 编译器通过类型推断自动确定 T 的具体类型(如 StringInteger)。


🔗 五、场景三:泛型接口 + 通配符 —— 键值对容器

🎯 目标

定义一个泛型接口 Pair<K,V>,实现类 OrderedPair,并使用通配符 ? 打印任意类型的 Pair 列表。

📝 代码(GenericInterfaceDemo.java

java

import java.util.ArrayList;
import java.util.List;

/**
 * 场景三:泛型接口 + 通配符
 * 定义一个泛型接口,并实现
 */
public class GenericInterfaceDemo {
    // 泛型接口 Pair<K, V>
    interface Pair<K, V> {
        K getKey();
        V getValue();
    }

    // 实现类,保留类型参数
    static class OrderedPair<K, V> implements Pair<K, V> {
        private K key;
        private V value;

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

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

    // 通配符示例:打印任意类型的 Pair 列表
    // 注意:这里的写法需要修正,应为 List<? extends Pair<?, ?>>
    public static void printPairs(List<? extends Pair<?, ?>> pairs) {
        for (Pair<?, ?> p : pairs) {
            System.out.println("Key: " + p.getKey() + ", Value: " + p.getValue());
        }
    }

    public static void main(String[] args) {
        System.out.println("=== 场景三:泛型接口与通配符 ===");

        List<Pair<String, Integer>> list = new ArrayList<>();
        list.add(new OrderedPair<>("Age", 25));
        list.add(new OrderedPair<>("Score", 98));
        list.add(new OrderedPair<>("Rank", 1));

        printPairs(list);
    }
}

✅ 运行结果

text

=== 场景三:泛型接口与通配符 ===
Key: Age, Value: 25
Key: Score, Value: 98
Key: Rank, Value: 1

📸 截图5GenericInterfaceDemo.java 代码及运行结果截图

🧠 核心知识点

  • 泛型接口定义了两个类型参数 <K, V>

  • 通配符 ? 表示未知类型。List<? extends Pair<?, ?>> 表示列表中的元素是 Pair 的某种子类型,且 Pair 的键值类型也未知。

  • PECS 原则:这里 List<? extends ...> 用于生产(读取)数据,符合 extends 作为上界。


🔬 六、进阶拔高:类型擦除与通配符原理

1. 类型擦除

泛型信息只在编译期存在,运行时会被擦除。上述 Box<String> 编译后实际上变成了 Box,所有 T 被替换为 Object

2. 通配符的设计意图

  • ? extends T:上界通配符,允许读取为 T,但不能写入(除了 null)。适用于生产者

  • ? super T:下界通配符,允许写入 T 及其子类型,但读取时只能得到 Object。适用于消费者

3. 无限定通配符 ?

Pair<?, ?> 表示键和值的类型都未知,只能读取并赋值给 Object


⚠️ 七、常见陷阱与解决方案

陷阱 说明 解决方案
不能创建泛型数组 new T[10] 编译错误 使用 ArrayList<T> 代替
静态域不能引用类型参数 static T instance 非法 将静态域移到非泛型类
基本类型不能作为类型参数 Box<int> 报错 使用包装类 Box<Integer>
运行时无法区分泛型类型 list instanceof List<String> 非法 使用通配符 list instanceof List<?>

📊 八、泛型 vs 非泛型 —— 终极对比

特性 非泛型(Object 泛型
类型安全 ❌ 运行时可能 ClassCastException ✅ 编译期检查
代码可读性 强制转换遍布 清晰,无需转换
灵活性 极高(可存任意类型) 有限但有界
性能 无开销 相同(擦除后)

💡 九、课后思考题

  1. 为什么不能创建泛型数组?有什么替代方案?

  2. 什么是 PECS 原则?举例说明 ? extends 和 ? super 的使用场景。

  3. 如何编写一个泛型方法,实现将任意类型的 List 复制到另一个 List 中?


📚 十、参考文献

  • Oracle. The Java™ Tutorials: Generics [Online].

  • Joshua Bloch. Effective Java (3rd Edition) Item 26–33.

  • 耿祥义, 张跃平. Java面向对象程序设计(第4版) 第8章.


🙏 写在最后

如果你觉得这篇文章对你有帮助,请点赞👍 + 收藏⭐ + 评论💬 支持一下!
你的鼓励是我持续输出硬核技术文章的动力。

更多推荐