
本文详细阐述如何利用java 8 stream api,将一个包含嵌套列表和异构数据类型的`map
引言:数据转换的挑战
在现代Java应用开发中,数据转换是常见的任务。我们经常需要将一种复杂的数据结构(例如,嵌套的Map或List)转换成另一种更适合业务逻辑或前端展示的简单结构(通常是数据传输对象DTO)。当源数据中包含异构类型(如同一字段存储整数、字符串或日期)时,这一转换过程会变得更具挑战性。
本教程将以一个具体场景为例:将一个Map
数据模型定义
首先,我们需要定义涉及到的数据模型,包括枚举、原始实体类和目标DTO类。
1. Value 枚举
这个枚举用于表示Person对象中的一个分类标签,例如不同的事件类型。
立即学习“Java免费学习笔记(深入)”;
public enum Value {
VALUE1,
VALUE2,
VALUE3
}2. Person 实体类
Person类代表原始数据结构中的个体记录。注意其中valueData字段被定义为Object类型,以容纳不同类型的数据(整数、字符串、日期)。eventDate字段使用LocalDate存储。
import java.time.LocalDate;
public class Person {
private String id; // 例如 "p1", "p2"
private Value tag; // 例如 VALUE1, VALUE2
private Object valueData; // 异构数据,例如 10, "Text", LocalDate
private LocalDate eventDate; // 例如 2000-10-10
private String message;
public Person(String id, Value tag, Object valueData, LocalDate eventDate, String message) {
this.id = id;
this.tag = tag;
this.valueData = valueData;
this.eventDate = eventDate;
this.message = message;
}
// Getters
public String getId() { return id; }
public Value getTag() { return tag; }
public Object getValueData() { return valueData; }
public LocalDate getEventDate() { return eventDate; }
public String getMessage() { return message; }
@Override
public String toString() {
return "Person{" +
"id='" + id + '\'' +
", tag=" + tag +
", valueData=" + valueData +
", eventDate=" + eventDate +
", message='" + message + '\'' +
'}';
}
}3. PersonDto 数据传输对象
PersonDto是转换后的目标对象,它扁平化了Person数据,并根据需求调整了字段类型(例如,将LocalDate转换为String)。
public class PersonDto {
private Value tag; // 对应 Person.tag
private String id; // 对应 Person.id
private String date; // 对应 Person.eventDate (格式化后)
private Object result; // 对应 Person.valueData (异构数据)
public PersonDto(Value tag, String id, String date, Object result) {
this.tag = tag;
this.id = id;
this.date = date;
this.result = result;
}
// Getters
public Value getTag() { return tag; }
public String getId() { return id; }
public String getDate() { return date; }
public Object getResult() { return result; }
@Override
public String toString() {
return "PersonDto{" +
"tag=" + tag +
", id='" + id + '\'' +
", date='" + date + '\'' +
", result=" + result +
'}';
}
}Stream API 实现数据转换
核心转换逻辑将利用Stream API的flatMap和map操作。
1. 初始化数据
为了演示,我们首先创建一些模拟数据:
import java.time.LocalDate;
import java.time.format.DateTimeFormatter;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
public class DataTransformer {
static DateTimeFormatter formatter = DateTimeFormatter.ofPattern("dd-MM-yyyy");
public static void main(String[] args) {
// 模拟原始数据 Map>
Map> personsData = new HashMap<>();
// Person p1 records
personsData.put("p1", Arrays.asList(
new Person("p1", Value.VALUE1, 10, LocalDate.of(2000, 10, 10), "Message"),
new Person("p1", Value.VALUE2, "Text", LocalDate.of(2000, 10, 10), "Message"),
new Person("p1", Value.VALUE3, LocalDate.of(2000, 11, 11), LocalDate.of(2000, 10, 10), "Message")
));
// Person p2 records
personsData.put("p2", Arrays.asList(
new Person("p2", Value.VALUE1, 20, LocalDate.of(2000, 10, 10), "Message"),
new Person("p2", Value.VALUE2, "Text", LocalDate.of(2000, 10, 10), "Message"),
new Person("p2", Value.VALUE3, LocalDate.of(2000, 11, 12), LocalDate.of(2000, 10, 10), "Message")
));
// ... (调用转换方法)
}
} 2. Stream 转换逻辑
现在,我们将personsData这个Map
public static void main(String[] args) {
// ... (数据初始化)
List resultDtos = personsData.values().stream()
// 1. 获取所有Person列表的Stream
// Stream> -> Stream
.flatMap(List::stream)
// 2. 将每个Person对象映射为PersonDto
.map(person -> new PersonDto(
person.getTag(), // 映射 Person.tag 到 PersonDto.tag
person.getId(), // 映射 Person.id 到 PersonDto.id
formatter.format(person.getEventDate()), // 格式化 Person.eventDate 到 PersonDto.date
person.getValueData() // 映射 Person.valueData (异构Object) 到 PersonDto.result
))
// 3. 收集结果到List
.collect(Collectors.toList());
// 打印结果
resultDtos.forEach(System.out::println);
}
解释:
-
personsData.values().stream(): 首先,我们从Map中获取所有List
的集合,并将其转换为一个Stream - >。
-
.flatMap(List::stream): 这一步是关键。由于我们有一个Stream,其中每个元素本身又是一个List
,flatMap操作用于将这些内部列表“扁平化”成一个单一的Stream 。这意味着,所有Person对象现在都在一个流中,可以进行统一处理。 -
.map(person -> new PersonDto(...)): 接下来,对于Stream
中的每一个Person对象,我们使用map操作将其转换成一个PersonDto实例。 - 在PersonDto的构造函数中,我们根据Person对象的属性进行赋值。
- person.getTag()直接映射到PersonDto.tag。
- person.getId()直接映射到PersonDto.id。
- formatter.format(person.getEventDate()):这里使用了DateTimeFormatter来将LocalDate类型的eventDate格式化为指定的String类型,并映射到PersonDto.date。
- person.getValueData():由于Person.valueData被定义为Object类型以处理异构数据,它被直接映射到PersonDto.result,保持其原始类型。
-
.collect(Collectors.toList()): 最后,collect操作将处理后的所有PersonDto对象收集到一个新的List
中。
完整示例代码
import java.time.LocalDate;
import java.time.format.DateTimeFormatter;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
// Value Enum
enum Value {
VALUE1,
VALUE2,
VALUE3
}
// Person Entity Class
class Person {
private String id;
private Value tag;
private Object valueData;
private LocalDate eventDate;
private String message;
public Person(String id, Value tag, Object valueData, LocalDate eventDate, String message) {
this.id = id;
this.tag = tag;
this.valueData = valueData;
this.eventDate = eventDate;
this.message = message;
}
public String getId() { return id; }
public Value getTag() { return tag; }
public Object getValueData() { return valueData; }
public LocalDate getEventDate() { return eventDate; }
public String getMessage() { return message; }
@Override
public String toString() {
return "Person{" +
"id='" + id + '\'' +
", tag=" + tag +
", valueData=" + valueData +
", eventDate=" + eventDate +
", message='" + message + '\'' +
'}';
}
}
// PersonDto DTO Class
class PersonDto {
private Value tag;
private String id;
private String date;
private Object result;
public PersonDto(Value tag, String id, String date, Object result) {
this.tag = tag;
this.id = id;
this.date = date;
this.result = result;
}
public Value getTag() { return tag; }
public String getId() { return id; }
public String getDate() { return date; }
public Object getResult() { return result; }
@Override
public String toString() {
return "PersonDto{" +
"tag=" + tag +
", id='" + id + '\'' +
", date='" + date + '\'' +
", result=" + result +
'}';
}
}
public class DataTransformer {
static DateTimeFormatter formatter = DateTimeFormatter.ofPattern("dd-MM-yyyy");
public static void main(String[] args) {
// 模拟原始数据 Map>
Map> personsData = new HashMap<>();
// Person p1 records
personsData.put("p1", Arrays.asList(
new Person("p1", Value.VALUE1, 10, LocalDate.of(2000, 10, 10), "Message"),
new Person("p1", Value.VALUE2, "Text", LocalDate.of(2000, 10, 10), "Message"),
new Person("p1", Value.VALUE3, LocalDate.of(2000, 11, 11), LocalDate.of(2000, 10, 10), "Message")
));
// Person p2 records
personsData.put("p2", Arrays.asList(
new Person("p2", Value.VALUE1, 20, LocalDate.of(2000, 10, 10), "Message"),
new Person("p2", Value.VALUE2, "Text", LocalDate.of(2000, 10, 10), "Message"),
new Person("p2", Value.VALUE3, LocalDate.of(2000, 11, 12), LocalDate.of(2000, 10, 10), "Message")
));
// 使用 Stream API 进行转换
List resultDtos = personsData.values().stream()
.flatMap(List::stream) // 扁平化 Stream> 到 Stream
.map(person -> new PersonDto(
person.getTag(),
person.getId(),
formatter.format(person.getEventDate()), // 格式化日期
person.getValueData() // 处理异构数据
))
.collect(Collectors.toList()); // 收集结果
// 打印转换后的 DTO 列表
System.out.println("转换后的 PersonDto 列表:");
resultDtos.forEach(System.out::println);
}
}
注意事项与总结
- 异构数据类型处理: 当某个字段需要存储多种类型的数据时,将其定义为Object是一个常见的策略。然而,在后续使用这些数据时,需要进行类型检查或强制类型转换,例如if (dto.getResult() instanceof Integer) { ... },这会增加代码的复杂性。考虑是否可以通过设计更具体的子类或使用泛型来









