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类型,这会导致:
- 类型不安全:可以向集合中添加任何类型的对象
- 需要频繁类型转换:取出元素时必须强制转换
- 运行时才能发现错误:类型错误只能在运行时通过异常发现
当采用泛型以后, 代码就会变得极其优雅
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(); // 编译器自动插入强制转换
常见的泛型符号
| 符号 | 含义 | 使用场景 |
|---|---|---|
| E | Element(元素) | 集合类中的元素,如List<E> |
| T | Type(类型) | 通用类型参数,如Class<T> |
| K | Key(键) | Map中的键,如Map<K, V> |
| V | Value(值) | Map中的值,如Map<K, V> |
| N | Number(数值) | 数值类型 |
| ? | 通配符 | 表示不确定的类型 |
我们重点讲一下? , 用下面代码来说明
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

731

被折叠的 条评论
为什么被折叠?



