我在数据库中有一个Person表,相关的域类如下所示:

public class Person {   
    
    private String firstName;
    private String secondName;
    private String city;
    private String country;
    private int age;
    private int hits;
    
    // accessors
} 

数据库表将有同一个人的多行,我需要使用Java 8聚合每个行的命中.

人员由以下字段唯一标识: firstName, secondName, city, country

我写了下面的逻辑,它给了我预期的结果

public void generateReport(List<PersonDTO> persons) {
        Map<String, Integer> personHitCount =
                persons
                        .stream()
                        .collect(Collectors.groupingBy(l -> getPersonKey(l), Collectors.summingInt(Person::getHits)));

        List<Person> reportRecords =
                persons
                        .stream().collect(Collectors.toMap(l -> getPersonKey(l), Function.identity(), (o1, o2) -> o1))
                        .entrySet()
                        .stream()
                        .filter(e -> personHitCount.containsKey(e.getKey()))
                        .map(e -> transform(e.getValue(), personHitCount.get(e.getKey())))
                        .collect(Collectors.toList());

        reportRecords.stream().forEach(System.out::println);
  }

private Person transform(Person personDTO, int count) {
    return new Person(
            personDTO.getFirstName(),
            personDTO.getSecondName(),
            personDTO.getCity(),
            personDTO.getCountry(),
            personDTO.getAge(),
            count);
}

private String getPersonKey(Person person) {
    return new StringJoiner("-")
            .add(person.getFirstName())
            .add(person.getSecondName())
            .add(person.getCity())
            .add(person.getCountry())
            .toString();
}

我不确定,这是否是一个好的和性能的方法,因为我循环两次人员列表.请您对这段代码提出任何改进或更好的方法.

推荐答案

具有相同返回类型的双迭代解

您可以通过删除一些不必要的操作来改进您的流,比如将DTO转换为String和过滤(您正在过滤来自您正在流传输的映射的键……).这将为您提供更紧凑、更具可读性的流.

正如@ daniu在 comments 部分所建议的,一个更好的方法是从SQL中获取总命中数,因为该语言为此目的提供了聚合函数.然而,我假设您可能没有这样做,因为您的SQL查询看起来像SELECT firstName, secondName, city, country, SUM(hits) FROM ...,留下了组中不需要的其他字段,但您需要构造一个Person实例(这只是一个猜测).

在这个解决方案中,我假设你的PersonDTO类通过提供一个基于属性firstNamesecondNamecitycountry的实现来遵守equals和hashcode契约.

public void generateReport2(List<PersonDTO> personsDTO) {
        List<Person> reportRecords = personsDTO.stream()
                .collect(Collectors.groupingBy(Function.identity(), Collectors.summingInt(PersonDTO::getHits))) //summing the total hits by DTO, where two DTOs are said equals by firstName, secondName, city, and country
                .entrySet().stream() //streaming the map's entries
                .map(e -> transform(e.getKey(), e.getValue())) //mapping each entry to a Person with the DTO (key) and the total hits (value)
                .collect(Collectors.toList()); //collecting the Person instance

        reportRecords.stream().forEach(System.out::println);
}
@Data
@NoArgsConstructor
@AllArgsConstructor
@EqualsAndHashCode(onlyExplicitlyIncluded = true)
public class PersonDTO {

        @EqualsAndHashCode.Include
        private String firstName;
        @EqualsAndHashCode.Include
        private String secondName;
        @EqualsAndHashCode.Include
        private String city;
        @EqualsAndHashCode.Include
        private String country;
        private int age;
        private int hits;
}

单次迭代和不同返回类型的解

我正在更新我的答案,以提供第二种解决方案,其中元素只迭代一次,有更大的改进.然而,它需要将方法的返回类型从List<Person>更改为Collection<Person>(我知道该方法是void的,但我假设它是打印结果而不是返回只是为了演示).

将返回类型从List<Person>更改为Collection<Person>将意味着在类层次 struct 中推广到更高的接口,因为CollectionList的父级,如果您已经与其他开发人员商定了特定的合约/接口,这可能是不可行的.这就是为什么我发布第二个解决方案作为更新,而不是作为实际答案.

简而言之,命中总数是使用Person个实例计算的,而不是整数.基本上,每个DTO都被用作一个键,每当两个DTO冲突(它们相等)时,它们对应的Person个命中被组合在一起,只保留产生的Person个实例.

public static void generateReport3(List<PersonDTO> personsDTO) {
    Collection<Person> reportRecords = personsDTO.stream() // Mapping each DTO to a Person and using the DTO as the key
            .collect(Collectors.toMap(Function.identity(), dto -> transform(dto, dto.getHits()), (p1, p2) -> { 
                p1.setHits(p1.getHits() + p2.getHits()); // Whenever two DTOs collide (they're equal), they combine the hits of their corresponding Person
                return p1;  //keeping the combined Person instance
            }))
            .values();  //Retrieving the Collection of Person

    reportRecords.stream().forEach(System.out::println);
}

演示版

这是OneCompiler的演示版本.在演示中,我假设PersonPersonDTO都具有相同的属性集,因为没有附加DTO类的实现.我还使用第二个解决方案更新了演示.

Java相关问答推荐

具有默认分支的JUnit代码覆盖率切换声明

int Array Stream System. out. print方法在打印Java8时在末尾添加% sign

Java WireMock定义存根在Cucumber并行执行的多线程测试中失败

JavaFX如何在MeshView中修复多个立方体?

JPanel透支重叠的JComcoBox

与Spring Boot相关的实体未正确保存

在Ubuntu 23.10上使用mp3创建JavaFX MediaPlayer时出错

如何在盒子的顶部和底部创建两张不同图片(大小相同)的盒子?

是否有一个Java Future实现可以在池繁忙时在调用者线程中执行?

通过Java列表中的某些字段搜索值

插入中的JOOQ序列,设置为VS值

判断重复的两个二维表算法?

在Oracle中调用输出参数在索引处缺少IN或OUT参数的函数

为什么JavaFX MediaPlayer音频播放在Windows和Mac上运行良好,但在Linux(POPOS/Ubuntu)上却有问题?

Kotlin-仅替换字符串中最后一个给定的字符串

如何在右击时 Select 新行?

当使用不同的参数类型调用时,为什么围绕Objects.equals的类型安全包装不会失败?

JavaFX中ListView中的问题

Vaadin Flow:设置密码显示按钮属性

找不到 jar 文件系统提供程序try 使用 jdeps 和 jlink 创建收缩 Java 映像来运行 Minecraft