深入浅出 Java 泛型(超详细)

泛型(Generics)是 Java 5 引入的核心特性,它让代码更安全、更灵活、更易于复用。本文从“为什么需要泛型”出发,系统讲解泛型的概念、语法、通配符、类型擦除及实际应用场景,助你彻底掌握 Java 类型系统的精髓。

一、为什么需要泛型?—— 告别 Object 的“野蛮时代”

在泛型出现之前,Java 集合类(如 ArrayListHashMap)使用 Object 存储元素:

// 泛型前:一个充满风险的 List
List list = new ArrayList();
list.add("Hello");
list.add(100); // 编译通过,但埋下隐患

String str = (String) list.get(0); // 强制转型
Integer num = (Integer) list.get(1); // 运行时抛出 ClassCastException!

问题

  1. 类型不安全:任何类型都能插入,编译期无法检查。

  2. 繁琐的强制转型:取元素时必须手动向下转型。

  3. 运行时异常风险:类型错误在运行时才暴露。


二、泛型是什么?—— 编译期的“类型契约”

泛型本质:将类型参数化,让类、接口、方法在定义时不指定具体类型,而在使用时再指定。

// 泛型拯救世界!
List<String> strList = new ArrayList<>(); // 指定元素类型为 String
strList.add("Java");
strList.add(100); // ❌ 编译错误!直接拒绝非String类型

String first = strList.get(0); // ✅ 无需强制转型,自动类型推断

核心价值

  • ✅ 类型安全:编译期检查类型一致性。

  • ✅ 消除强制转型:代码更简洁、可读性更强。

  • ✅ 代码复用:一份代码处理多种数据类型(如 List<T>)。


三、泛型基础语法—— 类、接口、方法

1. 泛型类(Generic Class)

// 定义一个泛型容器
public class Box<T> {
    private T content;

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

    public T getContent() {
        return content;
    }
}

// 使用
Box<String> stringBox = new Box<>();
stringBox.setContent("Generics");
String value = stringBox.getContent(); // 直接返回String

2. 泛型接口(Generic Interface)

public interface Repository<T> {
    void save(T entity);
    T findById(int id);
}

// 实现类指定具体类型
public class UserRepository implements Repository<User> {
    @Override
    public void save(User user) { ... }
    
    @Override
    public User findById(int id) { ... }
}

3. 泛型方法(Generic Method)

方法自己声明类型参数(可独立于类)

public class Utils {
    // 静态泛型方法:交换数组两个元素
    public static <T> void swap(T[] array, int i, int j) {
        T temp = array[i];
        array[i] = array[j];
        array[j] = temp;
    }
}

// 调用:类型自动推断
Integer[] nums = {1, 2, 3};
Utils.swap(nums, 0, 2); // 无需显式指定<Integer>

四、通配符 ? —— 泛型的“灵活控制器”

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

1. 上界通配符 <? extends T>

接受 T 或其子类(生产者场景,适合读取)

// 打印所有Shape及其子类(Circle, Rectangle)的列表
public void drawAll(List<? extends Shape> shapes) {
    for (Shape shape : shapes) {
        shape.draw();
    }
}

2. 下界通配符 <? super T>

接受 T 或其父类(消费者场景,适合写入)

// 将Circle对象放入一个“元素是Circle父类”的集合
public void addCircle(List<? super Circle> list) {
    list.add(new Circle());
}

3. 无界通配符 <?>

表示完全未知类型(极少用,可被 <T> 替代)

// 仅关心集合操作,不依赖元素类型
public void printSize(List<?> list) {
    System.out.println(list.size());
}

五、类型擦除 —— 泛型的“幕后真相”

Java 泛型是编译期特性,运行时类型信息会被擦除(Backward Compatibility)。

擦除规则

  • 泛型类/接口 → 原始类型(Raw Type),如 Box<T> → Box

  • 类型参数 T → 其上界(默认 Object

  • 必要时插入强制转型和类型检查

// 编译前
List<String> list = new ArrayList<>();
list.add("Hi");
String s = list.get(0);

// 编译后(等效代码)
List list = new ArrayList(); // 原始类型
list.add("Hi"); 
String s = (String) list.get(0); // 编译器插入转型

擦除带来的限制

  1. 不能创建泛型数组new T[size] ❌ → 用 ArrayList 替代。

  2. 无法使用 instanceof T:运行时类型已被擦除。

  3. 静态成员不能使用泛型类型:静态域/方法属于类,与实例无关。


六、泛型最佳实践 ✅

  1. 优先使用泛型集合List<String> 而非 List

  2. 避免混用原始类型(Raw Type):如 new ArrayList() 会绕过类型检查。

  3. PECS 原则(Producer-Extends, Consumer-Super)

    • 从集合读取 → <? extends T>

    • 向集合写入 → <? super T>

  4. 限制泛型上界<T extends Comparable<T>> 确保 T 可比较。

  5. 利用类型推断:多写 <>(钻石操作符),少写冗余类型。


七、典型应用场景

  1. 集合框架ArrayList<E>HashMap<K,V> 的核心基石。

  2. 函数式接口Comparator<T>Predicate<T>

  3. 工具类设计:如 Collections.sort()Optional<T>

  4. 通用算法:如排序、查找、缓存等可复用逻辑。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值