在java面向对象编程中,我们经常会遇到将不同但相关联的对象放入一个通用类型列表的场景,例如 list
为了解决这一问题,我们需要利用Java的多态特性,并结合适当的数据模型设计。以下将详细介绍几种有效的解决方案。
这是在不改变现有数据模型的情况下,最直接的解决方案。通过 instanceof 运算符判断列表中元素的实际类型,然后进行强制类型转换(向下转型),从而访问具体类型中定义的字段或方法。
核心思想:
当遍历 List
示例代码:
假设我们有以下类和接口定义:
public interface CommonDTO {
// 这是一个通用接口,不包含 CustId 字段
}
public class Emp implements CommonDTO {
private String custId; // 注意:Java 命名规范建议字段名小写开头
private String empId;
private String empName;
public Emp(String custId, String empId, String empName) {
this.custId = custId;
this.empId = empId;
this.empName = empName;
}
public String getCustId() {
return custId;
}
// 其他 getter/setter
}
public class Student implements CommonDTO {
private String custId;
private String studentId;
private String studentName;
public Student(String custId, String studentId, String studentName) {
this.custId = custId;
this.studentId = studentId;
this.studentName = studentName;
}
public String getCustId() {
return custId;
}
// 其他 getter/setter
}使用运行时类型检查提取 CustId:
import java.util.ArrayList;
import java.util.List;
public class DataExtractor {
/**
* 根据 CommonDTO 实例的实际类型获取其 CustId。
* @param dto CommonDTO 实例
* @return 如果实例是 Emp 或 Student,则返回其 CustId;否则返回 null。
*/
public String getCustIdFromCommonDTO(CommonDTO dto) {
if (dto instanceof Emp) {
return ((Emp) dto).getCustId();
} else if (dto instanceof Student) {
return ((Student) dto).getCustId();
}
return null; // 或者抛出异常,取决于业务需求
}
public static void main(String[] args) {
List commonList = new ArrayList<>();
commonList.add(new Emp("C001", "E101", "Alice"));
commonList.add(new Student("C002", "S201", "Bob"));
// 假设还有其他 CommonDTO 实现,但没有 CustId
commonList.add(new CommonDTO() { /* 匿名实现 */ });
DataExtractor extractor = new DataExtractor();
List custIds = new ArrayList<>();
System.out.println("--- 使用运行时类型检查提取 CustId ---");
for (CommonDTO comm : commonList) {
String custId = extractor.getCustIdFromCommonDTO(comm);
if (custId != null) {
custIds.add(custId);
System.out.println("提取到 CustId: " + custId);
} else {
System.out.println("无法从该对象提取 CustId: " + comm.getClass().getSimpleName());
}
}
System.out.println("所有提取到的 CustId: " + custIds);
}
} 注意事项:
为了提高代码的可维护性和扩展性,更推荐通过优化数据模型来解决此类问题。核心思想是引入一个共同的抽象,将 CustId 的访问方式标准化。
如果 CustId 字段只存在于 CommonDTO 的一个子集实现中,并且这些实现共享“拥有客户ID”这一业务特性,那么可以定义一个专门的接口来表示这一特性。
核心思想: 创建一个新的接口,例如 CustomerIdentifiable,其中定义 getCustId() 方法。然后让所有拥有 CustId 的 CommonDTO 实现类(如 Emp 和 Student)同时实现 CustomerIdentifiable 接口。
示例代码:
// 新增一个业务接口
public interface CustomerIdentifiable {
String getCustId();
}
// Emp 和 Student 实现新的接口
public class Emp implements CommonDTO, CustomerIdentifiable {
private String custId;
// ... 构造器和其它字段
@Override
public String getCustId() {
return custId;
}
}
public class Student implements CommonDTO, CustomerIdentifiable {
private String custId;
// ... 构造器和其它字段
@Override
public String getCustId() {
return custId;
}
}使用新的接口提取 CustId:
import java.util.ArrayList;
import java.util.List;
public class DataExtractorWithInterface {
public String getCustIdFromCommonDTO(CommonDTO dto) {
if (dto instanceof CustomerIdentifiable) {
return ((CustomerIdentifiable) dto).getCustId();
}
return null;
}
public static void main(String[] args) {
List commonList = new ArrayList<>();
commonList.add(new Emp("C001", "E101", "Alice"));
commonList.add(new Student("C002", "S201", "Bob"));
commonList.add(new CommonDTO() { /* 匿名实现,不实现 CustomerIdentifiable */ });
DataExtractorWithInterface extractor = new DataExtractorWithInterface();
List custIds = new ArrayList<>();
System.out.println("\n--- 使用公共业务接口提取 CustId ---");
for (CommonDTO comm : commonList) {
String custId = extractor.getCustIdFromCommonDTO(comm);
if (custId != null) {
custIds.add(custId);
System.out.println("提取到 CustId: " + custId);
} else {
System.out.println("无法从该对象提取 CustId: " + comm.getClass().getSimpleName());
}
}
System.out.println("所有提取到的 CustId: " + custIds);
}
} 优点:
如果 CustId 字段是 CommonDTO 的所有或大部分实现都共有的属性,并且这些实现之间存在共同的状态或行为,那么可以引入一个公共的抽象基类。
核心思想: 创建一个继承自 CommonDTO 的抽象基类(例如 AbstractCustomerDTO),并在其中定义 custId 字段和 getCustId() 方法。然后让 Emp 和 Student 等类继承这个基类。
示例代码:
// CommonDTO 保持不变(如果它是一个接口)
// public interface CommonDTO {}
// 或者如果 CommonDTO 是一个类,可以这样设计:
public class CommonDTO {
// 基础属性或方法
}
// 引入公共抽象基类
public abstract class AbstractCustomerDTO extends CommonDTO {
protected String custId; // 使用 protected 允许子类直接访问或通过 getter
public AbstractCustomerDTO(String custId) {
this.custId = custId;
}
public String getCustId() {
return custId;
}
}
// Emp 和 Student 继承新的基类
public class Emp extends AbstractCustomerDTO {
private String empId;
private String empName;
public Emp(String custId, String empId, String empName) {
super(custId);
this.empId = empId;
this.empName = empName;
}
// 其他 getter/setter
}
public class Student extends AbstractCustomerDTO {
private String studentId;
private String studentName;
public Student(String custId, String studentId, String studentName) {
super(custId);
this.studentId = studentId;
this.studentName = studentName;
}
// 其他 getter/setter
}使用新的基类提取 CustId:
import java.util.ArrayList;
import java.util.List;
public class DataExtractorWithBaseClass {
public String getCustIdFromCommonDTO(CommonDTO dto) {
if (dto instanceof AbstractCustomerDTO) {
return ((AbstractCustomerDTO) dto).getCustId();
}
return null;
}
public static void main(String[] args) {
List commonList = new ArrayList<>();
commonList.add(new Emp("C001", "E101", "Alice"));
commonList.add(new Student("C002", "S201", "Bob"));
commonList.add(new CommonDTO() { /* 匿名实现,不继承 AbstractCustomerDTO */ });
DataExtractorWithBaseClass extractor = new DataExtractorWithBaseClass();
List custIds = new ArrayList<>();
System.out.println("\n--- 使用公共抽象基类提取 CustId ---");
for (CommonDTO comm : commonList) {
String custId = extractor.getCustIdFromCommonDTO(comm);
if (custId != null) {
custIds.add(custId);
System.out.println("提取到 CustId: " + custId);
} else {
System.out.println("无法从该对象提取 CustId: " + comm.getClass().getSimpleName());
}
}
System.out.println("所有提取到的 CustId: " + custIds);
}
} 优点:
一旦通过上述方法之一(特别是引入公共业务接口或抽象基类)建立了统一的 CustId 访问机制,就可以利用 Java 8 引入的 Stream API 来更简洁、高效地从列表中提取 CustId。
核心思想:
使用 stream() 将 List
示例代码(基于引入公共业务接口的场景):
import java.util.ArrayList;
import java.util.List;
import java.util.stream.Collectors;
public class StreamApiExtractor {
public static void main(String[] args) {
List commonList = new ArrayList<>();
commonList.add(new Emp("C001", "E101", "Alice"));
commonList.add(new Student("C002", "S201", "Bob"));
commonList.add(new CommonDTO() { /* 匿名实现,不实现 CustomerIdentifiable */ });
commonList.add(new Emp("C003", "E102", "Charlie")); // 再添加一个 Emp
System.out.println("\n--- 使用 Stream API 提取 CustId ---");
List customerIds = commonList.stream()
.filter(dto -> dto instanceof CustomerIdentifiable) // 1. 过滤出实现 CustomerIdentifiable 的对象
.map(dto -> (CustomerIdentifiable) dto) // 2. 将 CommonDTO 转换为 CustomerIdentifiable
.map(CustomerIdentifiable::getCustId) // 3. 提取 CustId
.collect(Collectors.toList()); // 4. 收集到 List 中
System.out.println("通过 Stream API 提取到的 CustId: " + customerIds);
// 如果想同时打印提取过程
commonList.stream()
.filter(dto -> {
boolean isCustomer = dto instanceof CustomerIdentifiable;
if (!isCustomer) {
System.out.println("跳过非 CustomerIdentifiable 对象: " + dto.getClass().getSimpleName());
}
return isCustomer;
})
.map(dto -> {
CustomerIdentifiable customer = (CustomerIdentifiable) dto;
System.out.println("处理 CustomerIdentifiable 对象: " + dto.getClass().getSimpleName() + ", CustId: " + customer.getCustId());
return customer.getCustId();
})
.collect(Collectors.toList());
}
} Stream API 步骤解析:
优点:
从 List