Java中的泛型:从入门到精通

Java中的泛型:从入门到精通

泛型是Java中的核心概念之一,无论是在集合框架、工具类还是业务代码中都随处可见。最常见的就是List<T>Map<K,V>等集合类的泛型应用。今天我们将深入探讨Java泛型,帮助你真正理解并灵活运用这一特性。

一、为什么需要泛型?

泛型的引入主要是为了增强类型安全性减少类型转换。泛型不仅在Java中使用,在C#、C++、TypeScript等多种编程语言中都有类似的概念。

我们先假设没有泛型会怎么样
// 没有泛型的集合类(Java 1.4及以前)
List stringList = new ArrayList();
stringList.add("Hello");
stringList.add("World");

// 取出元素时需要强制类型转换
String str = (String) stringList.get(0);

// 更糟糕的是,可以添加任何类型,编译期无法发现错误
stringList.add(123); // 编译通过!
String wrong = (String) stringList.get(2); 
// 运行时抛出ClassCastException!

如果没有泛型,集合类只能存储Object类型,这会导致:

  1. 类型不安全:可以向集合中添加任何类型的对象
  2. 需要频繁类型转换:取出元素时必须强制转换
  3. 运行时才能发现错误:类型错误只能在运行时通过异常发现
当采用泛型以后, 代码就会变得极其优雅
List<String> stringList = new ArrayList<>();
stringList.add("Hello");
stringList.add("World");

// 无需类型转换
String str = stringList.get(0);

// 编译期就能发现类型错误
// stringList.add(123); // 编译错误!

泛型擦除

很多人不知道泛型其实是一个编译期概念, 这意味着泛型信息只存在于编译阶段, 编译后的字节码中会将泛型类型擦除, 替换为原始类型。

先来看这段代码

public class Container<T> {
    private T value;
    
    public void set(T value) {
        this.value = value;
    }
    
    public T get() {
        return value;
    }
}

// 使用
Container<String> stringContainer = new Container<>();
Container<Integer> intContainer = new Container<>();

我们把这段编译成Class文件, 再通过jad工具反编译成java文件得到如下代码:

public class Container {
    private Object value;
    
    public void set(Object value) {
        this.value = value;
    }
    
    public Object get() {
        return value;
    }
}

// 使用时编译器自动插入类型转换
Container stringContainer = new Container();
stringContainer.set("Hello"); // 实际调用: set((Object)"Hello")
String str = (String) stringContainer.get(); // 编译器自动插入强制转换

常见的泛型符号

符号含义使用场景
EElement(元素)集合类中的元素,如List<E>
TType(类型)通用类型参数,如Class<T>
KKey(键)Map中的键,如Map<K, V>
VValue(值)Map中的值,如Map<K, V>
NNumber(数值)数值类型
?通配符表示不确定的类型

我们重点讲一下? , 用下面代码来说明

public class WildcardDemo {
    public static void main(String[] args) {
        List<String> stringList = new ArrayList<>();
        stringList.add("Hello");
        
        // List<Object> 不能接收 List<String>
        // List<Object> objectList = stringList; // 编译错误!
        
        // List<?> 可以接收任何类型的List
        List<?> wildcardList = stringList; // 编译通过
        
        // 但是不能向List<?>中添加元素(除了null)
        // wildcardList.add("World"); // 编译错误!
        wildcardList.add(null); // 只能添加null
        
        // 可以读取元素,但只能作为Object
        Object obj = wildcardList.get(0);
    }
    
    // List<Object>作为参数
    public static void processObjectList(List<Object> list) {
        list.add("String");
        list.add(123);
        list.add(new Object());
    }
    
    // List<?>作为参数
    public static void processWildcardList(List<?> list) {
        // list.add("String"); // 编译错误!
        System.out.println("Size: " + list.size());
        Object obj = list.get(0); // 只能读取为Object
    }
}

核心区别:

  • List<Object>:明确指定元素类型为Object,可以添加任何对象
  • List<?>:未知类型的List,只能读取(作为Object),不能添加(类型安全考虑)

限定通配符

上界通配符 <? extends T> (上限)

表示类型是T或T的子类型,用于读取场景。

// 以动物体系为例
class Animal {
    public void eat() {
        System.out.println("Animal eating");
    }
}

class Dog extends Animal {
    public void bark() {
        System.out.println("Woof!");
    }
}

public class UpperBoundedDemo {
    // 可以接收Animal及其子类的List
    public static void feedAnimals(List<? extends Animal> animals) {
        for (Animal animal : animals) {
            animal.eat(); // 可以安全地调用Animal的方法
        }
        
        // 不能添加元素(除了null)
        // animals.add(new Dog()); // 编译错误!
        // 因为编译器不知道list的确切类型
    }
    
    public static void main(String[] args) {
        List<Dog> dogs = Arrays.asList(new Dog(), new Dog());
        feedAnimals(dogs); // 合法
        
        // 赋值演示
        List<Dog> dogList = new ArrayList<>();
        List<? extends Animal> animalList = dogList; // 合法
        
        // animalList.add(new Dog()); // 编译错误!
        Animal animal = animalList.get(0); // 可以读取为Animal
    }
}

下界通配符 <? super T> (下限)

表示类型是T或T的父类型,用于写入场景。

public class LowerBoundedDemo {
    // 可以接收Dog及其父类的List
    public static void addDogs(List<? super Dog> list) {
        list.add(new Dog()); // 可以安全地添加Dog
        // list.add(new Animal()); // 编译错误!只能添加Dog或Dog的子类
        
        // 读取时只能作为Object
        Object obj = list.get(0);
        // Dog dog = list.get(0); // 编译错误!
    }
    
    public static void main(String[] args) {
        List<Animal> animals = new ArrayList<>();
        List<Dog> dogs = new ArrayList<>();
        List<Object> objects = new ArrayList<>();
        
        addDogs(animals); // 合法:Animal是Dog的父类
        addDogs(dogs);    // 合法:Dog本身
        addDogs(objects); // 合法:Object是Dog的父类
        
        // 赋值演示
        List<Animal> animalList = new ArrayList<>();
        List<? super Dog> superList = animalList; // 合法
        
        superList.add(new Dog()); // 可以写入Dog
        Object obj = superList.get(0); // 读取时只能作为Object
    }
}

PECS原则:生产者与消费者

这是Josh Bloch在《Effective Java》中提出的重要原则:

  • Producer Extends: 如果需要从泛型对象读取数据(生产者),使用<? extends T>
  • Consumer Super: 如果需要向泛型对象写入数据(消费者),使用<? super T>
// 读取 -> extends -> 生产者(Producer)
List<? extends Animal> producers = ...;
Animal animal = producers.get(0); // ✓ 可以读取
// producers.add(new Dog()); // ✗ 不能写入

// 写入 -> super -> 消费者(Consumer)
List<? super Dog> consumers = ...;
consumers.add(new Dog()); // ✓ 可以写入
Object obj = consumers.get(0); // ✓ 只能读取为Object
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值