java 8、lambda、Objects copying: Creating a new list of Normalized objects
java 8, lambda , Objects copying: Creating a new list of Normalized objects
从 REST 服务中,我将获得作为员工列表的响应。其中可能包含以下定义的同一员工的多个地址。
[Employee{empId=1, name='Emp1', address='Emp1 Address1'},
Employee{empId=1, name='Emp1', address='Emp1 Address 2'},
Employee{empId=2, name='Emp2', address='Emp2 Address 1'}]
通过创建另一个列表,即 List<EmployeeNormalized >
,上面的响应需要以规范化的方式处理,如下定义。
[EmployeeNormalized{empId=1, name='Emp1',
addresses=[Emp1 Address1, Emp1 Address 2]},
EmployeeNormalized{empId=2, name='Emp2', addresses=[Emp2 Address 1]}]
代码片段:
class Employee {
private int empId;
private String name;
private String address;
// 50 other properties
public Employee(int empId, String name, String address) {
this.empId = empId;
this.name = name;
this.address = address;
}
// Setters and Getters
}
class EmployeeNormalized {
private int empId;
private String name;
private List<String> addresses;
// 50 other properties
public EmployeeNormalized(int empId, String name, List<String> address) {
this.empId = empId;
this.name = name;
this.addresses = address;
}
// Setters and Getters
}
List<EmployeeNormalized >
必须包含唯一的员工对象,并且 EmployeeNormalized
class 中的 List<String>
应该包含该员工的所有地址。
EDIT:
员工 class 拥有大约 50 个房产。
如何创建这种标准化形式的列表?
流解决方案:
public class Normalize {
public static void main(String[] args) {
List<Employee> employees = new ArrayList<>();
employees.add(new Employee(1, "Emp1", "Address 1"));
employees.add(new Employee(1, "Emp1", "Address 2"));
employees.add(new Employee(2, "Emp2", "Address 3"));
List<EmployeeNormalized> employeeNormalizedList = employees.stream()
.map(new Function<Employee, EmployeeNormalized>() {
private final Map<Integer, EmployeeNormalized> employeeIdMap = new HashMap<>();
@Override
public EmployeeNormalized apply(Employee employee) {
EmployeeNormalized normalized = this.employeeIdMap.computeIfAbsent(employee.getEmpId(),
key -> new EmployeeNormalized(employee.getEmpId(), employee.getName(), new ArrayList<>()));
normalized.getAddresses().add(employee.getAddress());
return normalized;
}
})
.distinct()
.collect(Collectors.toList());
employeeNormalizedList.forEach(System.out::println);
}
}
相当复杂,需要调用 distinct 来消除重复实例。
我会选择简单的循环:
public class Normalize {
public static void main(String[] args) {
List<Employee> employees = new ArrayList<>();
employees.add(new Employee(1, "Emp1", "Address 1"));
employees.add(new Employee(1, "Emp1", "Address 2"));
employees.add(new Employee(2, "Emp2", "Address 3"));
Map<Integer, EmployeeNormalized> employeeIdMap = new HashMap<>();
for (Employee employee : employees) {
EmployeeNormalized normalized = employeeIdMap.computeIfAbsent(employee.getEmpId(), key -> new EmployeeNormalized(employee.getEmpId(), employee.getName(), new ArrayList<>()));
normalized.getAddresses().add(employee.getAddress());
}
List<EmployeeNormalized> employeeNormalizedList = new ArrayList<>(employeeIdMap.values());
employeeNormalizedList.forEach(System.out::println);
}
}
基本上这两种解决方案都使用员工id作为唯一标识符,并将实例映射到id。如果id是第一次遇到,则创建实例并添加地址,如果已经有该id的实例,则获取实例并添加地址。
编辑:
由于 computeIfAbsent
由于许多属性而不受欢迎,因此您可以不添加参数构造函数并使用 setter 传递 属性 值。最好的选择是使用映射库,然后你也可以用 computeIfAbsent
来做。
public class Normalize {
public static void main(String[] args) {
List<Employee> employees = new ArrayList<>();
employees.add(new Employee(1, "Emp1", "Address 1"));
employees.add(new Employee(1, "Emp1", "Address 2"));
employees.add(new Employee(2, "Emp2", "Address 3"));
Map<Integer, EmployeeNormalized> employeeIdMap2 = new HashMap<>();
for (Employee employee : employees) {
int id = employee.getEmpId();
EmployeeNormalized normalized = employeeIdMap2.get(id);
if (normalized == null) {
normalized = new EmployeeNormalized();
normalized.setEmpId(id);
normalized.setName(employee.getName());
normalized.setAddresses(new ArrayList<>());
//set other properties
//or even better use mapping library to create normalized and transfer property values
employeeIdMap2.put(id, normalized);
}
normalized.getAddresses().add(employee.getAddress());
}
List<EmployeeNormalized> employeeNormalizedList2 = new ArrayList<>(employeeIdMap2.values());
employeeNormalizedList2.forEach(System.out::println);
}
}
这个任务可以用 Stream API 来解决,前提是有一些包装器 class/record 来表示 key(employeeId 和 employeeName)。即使是普通的 ArrayList<Object>
也可以用于此目的,但是引入 record
因为 Java 16 最好使用它们。
解决方案本身非常简单:使用 Collectors.groupingBy
创建一个键,Collectors.mapping
构建每个员工的地址列表,最后加入键(employeeId
和 employeeName
) 和 EmployeeNormalized
:
中地址的值列表
//
List<Employee> employees = .... ; // build employee list
List<EmployeeNormalized> normEmployees = employees
.stream()
.collect(Collectors.groupingBy(
emp -> Arrays.asList(emp.getEmpId(), emp.getName()),
LinkedHashMap::new, // maintain insertion order
Collectors.mapping(Employee::getAddress, Collectors.toList())
)) // Map<List<Object>, List<String>>
.entrySet()
.stream()
.map(e -> new EmployeeNormalized(
((Integer) e.getKey().get(0)).intValue(), // empId
(String) e.getKey().get(1), // name
e.getValue()
))
.collect(Collectors.toList());
使用 record
可以在不进行额外转换的情况下维护类型安全的键。
Record 可以用一个小包装器 class 代替,以表示具有 hashCode
/ equals
方法被覆盖的键,因为此 class 的实例用作中间映射中的键。
// Java 16+
record EmpKey(int empId, String name) {}
List<EmployeeNormalized> normEmployees = employees
.stream()
.collect(Collectors.groupingBy(
emp -> new EmpKey(emp.getEmpId(), emp.getName()),
LinkedHashMap::new, // maintain insertion order
Collectors.mapping(Employee::getAddress, Collectors.toList())
)) // Map<List<Object>, List<String>>
.entrySet()
.stream()
.map(e -> new EmployeeNormalized(
e.getKey().empId(), e.getKey().name(), e.getValue()
))
.collect(Collectors.toList());
从 REST 服务中,我将获得作为员工列表的响应。其中可能包含以下定义的同一员工的多个地址。
[Employee{empId=1, name='Emp1', address='Emp1 Address1'},
Employee{empId=1, name='Emp1', address='Emp1 Address 2'},
Employee{empId=2, name='Emp2', address='Emp2 Address 1'}]
通过创建另一个列表,即 List<EmployeeNormalized >
,上面的响应需要以规范化的方式处理,如下定义。
[EmployeeNormalized{empId=1, name='Emp1',
addresses=[Emp1 Address1, Emp1 Address 2]},
EmployeeNormalized{empId=2, name='Emp2', addresses=[Emp2 Address 1]}]
代码片段:
class Employee {
private int empId;
private String name;
private String address;
// 50 other properties
public Employee(int empId, String name, String address) {
this.empId = empId;
this.name = name;
this.address = address;
}
// Setters and Getters
}
class EmployeeNormalized {
private int empId;
private String name;
private List<String> addresses;
// 50 other properties
public EmployeeNormalized(int empId, String name, List<String> address) {
this.empId = empId;
this.name = name;
this.addresses = address;
}
// Setters and Getters
}
List<EmployeeNormalized >
必须包含唯一的员工对象,并且 EmployeeNormalized
class 中的 List<String>
应该包含该员工的所有地址。
EDIT:
员工 class 拥有大约 50 个房产。
如何创建这种标准化形式的列表?
流解决方案:
public class Normalize {
public static void main(String[] args) {
List<Employee> employees = new ArrayList<>();
employees.add(new Employee(1, "Emp1", "Address 1"));
employees.add(new Employee(1, "Emp1", "Address 2"));
employees.add(new Employee(2, "Emp2", "Address 3"));
List<EmployeeNormalized> employeeNormalizedList = employees.stream()
.map(new Function<Employee, EmployeeNormalized>() {
private final Map<Integer, EmployeeNormalized> employeeIdMap = new HashMap<>();
@Override
public EmployeeNormalized apply(Employee employee) {
EmployeeNormalized normalized = this.employeeIdMap.computeIfAbsent(employee.getEmpId(),
key -> new EmployeeNormalized(employee.getEmpId(), employee.getName(), new ArrayList<>()));
normalized.getAddresses().add(employee.getAddress());
return normalized;
}
})
.distinct()
.collect(Collectors.toList());
employeeNormalizedList.forEach(System.out::println);
}
}
相当复杂,需要调用 distinct 来消除重复实例。
我会选择简单的循环:
public class Normalize {
public static void main(String[] args) {
List<Employee> employees = new ArrayList<>();
employees.add(new Employee(1, "Emp1", "Address 1"));
employees.add(new Employee(1, "Emp1", "Address 2"));
employees.add(new Employee(2, "Emp2", "Address 3"));
Map<Integer, EmployeeNormalized> employeeIdMap = new HashMap<>();
for (Employee employee : employees) {
EmployeeNormalized normalized = employeeIdMap.computeIfAbsent(employee.getEmpId(), key -> new EmployeeNormalized(employee.getEmpId(), employee.getName(), new ArrayList<>()));
normalized.getAddresses().add(employee.getAddress());
}
List<EmployeeNormalized> employeeNormalizedList = new ArrayList<>(employeeIdMap.values());
employeeNormalizedList.forEach(System.out::println);
}
}
基本上这两种解决方案都使用员工id作为唯一标识符,并将实例映射到id。如果id是第一次遇到,则创建实例并添加地址,如果已经有该id的实例,则获取实例并添加地址。
编辑:
由于 computeIfAbsent
由于许多属性而不受欢迎,因此您可以不添加参数构造函数并使用 setter 传递 属性 值。最好的选择是使用映射库,然后你也可以用 computeIfAbsent
来做。
public class Normalize {
public static void main(String[] args) {
List<Employee> employees = new ArrayList<>();
employees.add(new Employee(1, "Emp1", "Address 1"));
employees.add(new Employee(1, "Emp1", "Address 2"));
employees.add(new Employee(2, "Emp2", "Address 3"));
Map<Integer, EmployeeNormalized> employeeIdMap2 = new HashMap<>();
for (Employee employee : employees) {
int id = employee.getEmpId();
EmployeeNormalized normalized = employeeIdMap2.get(id);
if (normalized == null) {
normalized = new EmployeeNormalized();
normalized.setEmpId(id);
normalized.setName(employee.getName());
normalized.setAddresses(new ArrayList<>());
//set other properties
//or even better use mapping library to create normalized and transfer property values
employeeIdMap2.put(id, normalized);
}
normalized.getAddresses().add(employee.getAddress());
}
List<EmployeeNormalized> employeeNormalizedList2 = new ArrayList<>(employeeIdMap2.values());
employeeNormalizedList2.forEach(System.out::println);
}
}
这个任务可以用 Stream API 来解决,前提是有一些包装器 class/record 来表示 key(employeeId 和 employeeName)。即使是普通的 ArrayList<Object>
也可以用于此目的,但是引入 record
因为 Java 16 最好使用它们。
解决方案本身非常简单:使用 Collectors.groupingBy
创建一个键,Collectors.mapping
构建每个员工的地址列表,最后加入键(employeeId
和 employeeName
) 和 EmployeeNormalized
:
//
List<Employee> employees = .... ; // build employee list
List<EmployeeNormalized> normEmployees = employees
.stream()
.collect(Collectors.groupingBy(
emp -> Arrays.asList(emp.getEmpId(), emp.getName()),
LinkedHashMap::new, // maintain insertion order
Collectors.mapping(Employee::getAddress, Collectors.toList())
)) // Map<List<Object>, List<String>>
.entrySet()
.stream()
.map(e -> new EmployeeNormalized(
((Integer) e.getKey().get(0)).intValue(), // empId
(String) e.getKey().get(1), // name
e.getValue()
))
.collect(Collectors.toList());
使用 record
可以在不进行额外转换的情况下维护类型安全的键。
Record 可以用一个小包装器 class 代替,以表示具有 hashCode
/ equals
方法被覆盖的键,因为此 class 的实例用作中间映射中的键。
// Java 16+
record EmpKey(int empId, String name) {}
List<EmployeeNormalized> normEmployees = employees
.stream()
.collect(Collectors.groupingBy(
emp -> new EmpKey(emp.getEmpId(), emp.getName()),
LinkedHashMap::new, // maintain insertion order
Collectors.mapping(Employee::getAddress, Collectors.toList())
)) // Map<List<Object>, List<String>>
.entrySet()
.stream()
.map(e -> new EmployeeNormalized(
e.getKey().empId(), e.getKey().name(), e.getValue()
))
.collect(Collectors.toList());