我正在使用ApachePOI从EXCEL文件中读取数据,并将其转换为对象列表.但现在我想将基于特定规则的任何重复项提取到该对象的另一个列表中,并获得非重复项列表.
判断重复项的条件

  1. 名字
  2. 邮箱
  3. 电话号码
  4. 商品及服务税编号

这些属性中的任何一个都可能导致重复.这意味着or分而不是and

Party Class

public class Party {

    private String 名字;

    private Long number;

    private String 邮箱;

    private String address;

    private BigDecimal openingBalance;

    private LocalDateTime openingDate;

    private String gstNumber;
   // Getter Setter Skipped
}

假设这是到目前为止由数据处理逻辑返回的列表

    var firstParty = new Party();
    firstParty.setName("Valid Party");
    firstParty.setAddress("Valid");
    firstParty.setEmail("Valid");
    firstParty.setGstNumber("Valid");
    firstParty.setNumber(1234567890L);
    firstParty.setOpeningBalance(BigDecimal.ZERO);
    firstParty.setOpeningDate(DateUtil.getDDMMDateFromString("01/01/2020"));

    var secondParty = new Party();
    secondParty.setName("Valid Party");
    secondParty.setAddress("Valid Address");
    secondParty.setEmail("Valid Email");
    secondParty.setGstNumber("Valid GST");
    secondParty.setNumber(7593612247L);
    secondParty.setOpeningBalance(BigDecimal.ZERO);
    secondParty.setOpeningDate(DateUtil.getDDMMDateFromString("01/01/2020"));

    var thirdParty = new Party();
    thirdParty.setName("Valid Party 1");
    thirdParty.setAddress("address");
    thirdParty.setEmail("邮箱");
    thirdParty.setGstNumber("gst");
    thirdParty.setNumber(7593612888L);
    thirdParty.setOpeningBalance(BigDecimal.ZERO);
    secondParty.setOpeningDate(DateUtil.getDDMMDateFromString("01/01/2020"));

    var validParties = List.of(firstParty, secondParty, thirdParty);

到目前为止,我所try 的是:


var partyNameOccurrenceMap = validParties.parallelStream()
        .map(Party::getName)
        .collect(Collectors.groupingBy(Function.identity(), HashMap::new, Collectors.counting()));

var partyNameOccurrenceMapCopy = SerializationUtils.clone(partyNameOccurrenceMap);

var duplicateParties = validParties.stream()
        .filter(party-> {
            var occurrence = partyNameOccurrenceMap.get(party.getName());
            if (occurrence > 1) {
                partyNameOccurrenceMap.put(party.getName(), occurrence - 1);
                return true;
            }
            return false;
        })
        .toList();
var nonDuplicateParties = validParties.stream()
        .filter(party -> {
            var occurrence = partyNameOccurrenceMapCopy.get(party.getName());
            if (occurrence > 1) {
                partyNameOccurrenceMapCopy.put(party.getName(), occurrence - 1);
                return false;
            }
            return true;
        })
        .toList();

上面的代码只判断party 名字,但我们还需要判断邮箱电话号码商品及服务税编号.

上面写的代码运行得很好,但可读性、简洁性和性能可能会有问题,因为数据集足够大,比如EXCEL文件中的10k行

推荐答案

Never ignore Equals/hashCode contract

nameemailnumbergstNumber

这些属性中的任何一个都可能导致重复,这意味着or

你对重复的implies的定义,这些属性中的任何一个都应该匹配,而其他属性可能是not.

这意味着不可能提供与给定定义匹配且不违反hashCode contract的实现equals/hashCode.

如果根据equals方法,两个对象相等,则在这两个对象的每个对象上调用hashCode方法必须产生same integer result.

I.e. if you implement equals in such a way they any (not all) of these properties: nameemailnumbergstNumber could match, and that would enough to consider the two objects equal, then there's no way to implement hashCode correctly.

因此,您不能在基于散列的集合中使用带有损坏的equals/hashCode实现的对象,因为相同的对象可能会出现在不同的存储桶中(因为它们可能会产生不同的散列).即HashMap将不能识别复制的密钥,因此具有groupingBy()Function.identity()作为classifier功能的groupingBy将不能正常工作.

Therefore, to address this problem, you need to implement equals() based on all 4 properties: nameemailnumbergstNumber (i.e. all these values have to be equal), and similarly all these values must contribute to hash-code.

How to determine Duplicates

没有简单的方法来通过多个标准来确定重复项.你们提供的解决方案not是可行的,因为我们不能依赖equals/hashCode.

唯一的方法是 for each End每个属性分别生成HashMap(即,在本例中我们需要4个贴图).但我们能不能另辟蹊径,避免对每个 map 重复相同的步骤,并对逻辑进行硬编码?

是的,我们可以.

我们可以创建一个定制的泛型累加类型(它适用于任何类--没有硬编码逻辑),它将封装确定重复项的所有逻辑,并在幕后维护任意数量的映射.在使用给定集合中的所有元素后,此自定义对象将知道其中的所有重复项.

这就是它可以被实施的方式.

将用作定制Collector的容器的定制累计类型.它的构造函数要求varargs为functions,每个函数对应于在判断对象是否重复时应考虑的属性.

public static class DuplicateChecker<T> implements Consumer<T> {
    
    private List<DuplicateHandler<T>> handles;
    private Set<T> duplicates;

    @SafeVarargs
    public DuplicateChecker(Function<T, ?>... keyExtractors) {
        this.handles = Arrays.stream(keyExtractors)
            .map(DuplicateHandler::new)
            .toList();
    }

    @Override
    public void accept(T t) {
        handles.forEach(h -> h.accept(t));
    }
    
    public DuplicateChecker<T> merge(DuplicateChecker<T> other) {
        for (DuplicateHandler<T> handler: handles) {
            other.handles.forEach(handler::merge);
        }
        
        return this;
    }

    public DuplicateChecker<T> finish() {
        duplicates = handles.stream()
            .flatMap(handler -> handler.getDuplicates().stream())
            .flatMap(Set::stream)
            .collect(Collectors.toSet());
        
        return this;
    }
    
    public boolean isDuplicate(T t) {
        return duplicates.contains(t);
    }
}

一个帮助器类,表示封装HashMap的单个创建数(如nameemail等).keyExtractor用于从类型T的对象获取key.

public static class DuplicateHandler<T> implements Consumer<T> {
    private Map<Object, Set<T>> itemByKey = new HashMap<>();
    private Function<T, ?> keyExtractor;

    public DuplicateHandler(Function<T, ?> keyExtractor) {
        this.keyExtractor = keyExtractor;
    }

    @Override
    public void accept(T t) {
        itemByKey.computeIfAbsent(keyExtractor.apply(t), k -> new HashSet<>()).add(t);
    }

    public void merge(DuplicateHandler<T> other) {
        other.itemByKey.forEach((k, v) ->
            itemByKey.merge(k,v,(oldV, newV) -> { oldV.addAll(newV); return oldV; }));
    }
    
    public Collection<Set<T>> getDuplicates() {
        Collection<Set<T>> duplicates = itemByKey.values();
        duplicates.removeIf(set -> set.size() == 1); // the object is proved to be unique by this particular property
        
        return duplicates;
    }
}

这就是负责生成副本映射的方法,该方法将从干净的代码中使用.给定的集合将被划分为两个部分:一个映射到keytrue个副本,另一个映射到keyfalse-唯一对象.

public static <T> Map<Boolean, List<T>> getPartitionByProperties(Collection<T> parties,
                                                                 Function<T, ?>... keyExtractors) {
    
    DuplicateChecker<T> duplicateChecker = parties.stream()
        .collect(Collector.of(
            () -> new DuplicateChecker<>(keyExtractors),
            DuplicateChecker::accept,
            DuplicateChecker::merge,
            DuplicateChecker::finish
        ));
    
    return parties.stream()
        .collect(Collectors.partitioningBy(duplicateChecker::isDuplicate));
}

这就是你如何将它应用到你的特定 case 中.

main()

public static void main(String[] args) {
    List<Party> parties = // initializing the list of parties
    
    Map<Boolean, List<Party>> isDuplicate = partitionByProperties(parties,
            Party::getName, Party::getNumber,
            Party::getEmail, Party::getGstNumber);
}

Java相关问答推荐

如何审查Java dtos中的自定义注释字段?

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

使用JdkClientHttpRequestFactory通过Spring RestClient和Wiemock读取时达到EOF

无法在org. openjfx:javafx—fxml:21的下列变体之间进行 Select

Java LocalTime.parse在本地PC上的Spring Boot中工作,但在Docker容器中不工作

基于调车场算法的科学计算器

使用GridBagLayout正确渲染

如何对多个字段进行分组和排序?

如何在Application.yaml中连接字符串?

如何将Java文档配置为在指定的项目根目录中生成?

为什么Collectors.toList()不能保证易变性

Android应用程序为错误的显示类型 Select 尺寸文件

Quarkus:运行时出现EnumConstantNotPresentException

在实例化中指定泛型类型与不指定泛型类型之间的区别

JavaFX:为什么我的ComboBox添加了一个不必要的单元格的一部分?

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

由于版本不匹配,从Java 8迁移到Java 17和Spring 6 JUnit4失败

使用Java线程进行并行编程

转换为JSON字符串时,日期按天递减-Java

为什么当我输入变量而不是直接输入字符串时,我的方法不起作用?