PECS(Producer Extends, Consumer Super)是Java泛型编程中的一个重要原则,用于指导如何使用通配符以增强代码的灵活性和可重用性。PECS原则可以分为两个部分:
-
Producer Extends:
- 当你需要从某个集合中读取数据时,应该使用
? extends T
。这表示该集合可以是T
或其子类的类型。 - 适用于生产者(提供数据)的场景。
- 当你需要从某个集合中读取数据时,应该使用
-
Consumer Super:
- 当你需要向某个集合中写入数据时,应该使用
? super T
。这表示该集合可以是T
或其父类的类型。 - 适用于消费者(接收数据)的场景。
- 当你需要向某个集合中写入数据时,应该使用
遵循PECS原则的好处
- 类型安全:使用PECS原则可以确保在编译时进行类型检查,从而减少运行时错误。
- 代码灵活性:允许更广泛的类型使用,使得代码更加灵活和可重用。
- 简化复杂性:通过明确区分生产者和消费者的行为,可以使代码的意图更加清晰。
Java示例
java.util.Collections.copy方法使用了pecs原则
下面是一个示例,展示如何遵循PECS原则。我们将创建一个简单的类来演示生产者和消费者的使用。
示例代码
import java.util.ArrayList;
import java.util.List;
// 定义一个动物类及其子类
class Animal {
public void makeSound() {
System.out.println("Some sound");
}
}
class Dog extends Animal {
@Override
public void makeSound() {
System.out.println("Woof");
}
}
class Cat extends Animal {
@Override
public void makeSound() {
System.out.println("Meow");
}
}
// 使用PECS原则的生产者方法
public class PECSExample {
// 生产者方法:接受一个包含Animal或其子类的列表
public static void printSounds(List<? extends Animal> animals) {
for (Animal animal : animals) {
animal.makeSound(); // 调用每个动物的发声方法
}
}
// 消费者方法:接受一个包含Animal或其父类的列表
public static void addDog(List<? super Dog> dogs) {
dogs.add(new Dog()); // 可以添加Dog对象
}
public static void main(String[] args) {
List<Dog> dogList = new ArrayList<>();
dogList.add(new Dog());
List<Cat> catList = new ArrayList<>();
catList.add(new Cat());
// 使用生产者方法
System.out.println("Animal Sounds:");
printSounds(dogList); // 输出:Woof
printSounds(catList); // 输出:Meow
// 使用消费者方法
List<Animal> animalList = new ArrayList<>();
addDog(animalList); // 添加Dog到Animal列表
for (Animal animal : animalList) {
animal.makeSound(); // 输出:Woof
}
}
}
代码分析
-
动物类:
Animal
是一个基类,Dog
和Cat
是其子类,分别实现了makeSound()
方法。
-
生产者方法:
printSounds(List<? extends Animal> animals)
方法接受一个Animal
或其子类的列表,并打印每个动物的声音。
-
消费者方法:
addDog(List<? super Dog> dogs)
方法接受一个Dog
或其父类的列表,并向其中添加Dog
对象。
-
主方法:
- 创建
Dog
和Cat
的列表,并调用生产者方法打印它们的声音。 - 创建一个
Animal
类型的列表并使用消费者方法添加Dog
对象。
- 创建
SOLID中的里氏替换原则
// 入参是RedDog或者是它的父类
int sumInt(List<? super RedDog> list) {
list.add(new RedDog("d1", true));
list.forEach(o -> {
((RedDog) o).makeSound();// 直接使用RedDog对象,符合里氏替换的原则,子类可以替换父类
});
return 0;
}
// 方法内部为父类集合赋值,在外面使用时,暴露出父类(子类公共字段和方法)来访问
List<? extends Dog> getDogs() {
List<Dog> list = new ArrayList<>(); // 返回的集合中,有多种Dog的子类,在调用方使用时,是以dog的方式对外暴露的
list.add(new RedDog("d1", true));
list.add(new RedDog("d2", true));
list.add(new BlackDog("black1", false));
return list;
}
总结
通过遵循PECS原则,我们能够更好地管理泛型类型的使用,提高代码的灵活性和可读性,同时保持类型安全。这种方式使得开发者在处理复杂数据结构时能够更有效地组织代码。