【问题标题】:java 8, lambda , Objects copying: Creating a new list of Normalized objectsjava 8,lambda,对象复制:创建规范化对象的新列表
【发布时间】:2022-02-03 16:02:20
【问题描述】:

从 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]}]

代码sn-p:

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&lt;EmployeeNormalized &gt; 必须包含唯一的员工对象,EmployeeNormalized 类中的 List&lt;String&gt; 应该包含该员工的所有地址。

EDIT:Employee 类有大约 50 个属性。

如何创建这种标准化形式的列表?

【问题讨论】:

  • 将属性替换为旧值列表的属性是非规范化(从 1NF)而不是规范化(到 1NF)。 PS请阅读编辑帮助重新块和内联格式代码和文本和重新换行符。请在编辑框下方查看您帖子的格式化版本。 (我已编辑。)

标签: java list stream database-normalization denormalization


【解决方案1】:

流解决方案:

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);
    }
}

【讨论】:

  • 在我的例子中,Employee 类有大约 50 多个属性。在 computeIfAbsent() 方法中映射单个属性有点困难。
  • @Sang 第二种方法是最好的。这是应该包含在您的问题中的基本信息。你真的是说构造函数有50个项目要处理吗?您确实需要编辑您的问题并使用可能影响任何提议设计的信息进行更新。
  • @SVK 属性数量有什么关系? 5、50 或 500,您仍然需要创建 EmployeeNormalized 的新实例并将属性值从源传输到目标。这种映射通常也不是手动完成的,而是使用库完成的。 ModelMapper、MapStruct、Dozer - 仅举几例,还有更多。
  • @SVK 您可以检查编辑以查看它是否适合您。
【解决方案2】:

这个任务可以通过 Stream API 来解决,前提是有一些包装类/记录来表示一个(employeeId 和employeeName)。即使是普通的ArrayList&lt;Object&gt; 也可以用于此目的,但自从 Java 16 以来引入了record,最好使用它们。

解决方案本身非常简单:使用Collectors.groupingBy 创建一个键,Collectors.mapping 构建每个员工的地址列表,最后加入键(employeeIdemployeeName)和值列表地址在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 可以替换为一个小的包装类来表示一个键,并重写了 hashCode / equals 方法,因为此类的实例用作中间映射中的键。

// 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());

【讨论】:

    猜你喜欢
    • 2019-01-15
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2018-05-25
    • 1970-01-01
    • 2019-07-26
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多