Java 8中两个HashSet之间的交集

问题描述 投票:1回答:3

我有一个对象如下:

public Class MyObjDTO {
    private Long id;
    private Boolean checked;

    //getter and setters

    @Override
    public final int hashCode() {
        Long id = getId();
        return (id == null ? super.hashCode() : id.hashCode());
    }

    @Override
    public boolean equals(final Object obj) {
        if (this == obj)
            return true;
        if (!(obj instanceof MyObjDTO))
            return false;
        Long id = getId();
        Long objId = ((MyObjDTO) obj).getId();
        if (id.equals(objId)) {
            return true;
        } else {
            return false;
        }
    }
}

我有两个哈希集,包含来自此对象的一些实例:

HashSet oldSet = new HashSet();
oldSet.add(new MyObjDTO(1,true));
oldSet.add(new MyObjDTO(2,true));
oldSet.add(new MyObjDTO(3,false));

HashSet newSet = new HashSet();
newSet.add(new MyObjDTO(1,false));
newSet.add(new MyObjDTO(2,true));
newSet.add(new MyObjDTO(4,true));

所以我想在这里做的是选择newSet而不是oldSet中的对象,在这种情况下它是:new MyObjDTO(4,true),我用它做了:

Stream<MyObjDTO> toInsert = newSet.stream().filter(e -> !oldSet.contains(e));

然后我想选择oldSet中的对象而不是newSet中的对象,在本例中为:new MyObjDTO(3,false),我用它做了:

Stream<MyObjDTO> toRemove = oldSet.stream().filter(e -> !newSet.contains(e));

最后一步是我要选择newSet和oldSet中的对象,但它们对属性checked有不同的值,在这种情况下它是:new MyObjDTO(1,false)。我试过的是这个:

Stream<MyObjDTO> toUpdate = oldSet.stream().filter(newSet::contains);

但是这个将返回new MyObjDTO(1,false)new MyObjDTO(2,true)

我怎么解决这个问题?

collections java-8 java-stream hashset
3个回答
1
投票

首先,你的equals()hashCode()方法违反了他们的基本合同。根据javadoc of hashCode()

如果两个对象根据equals(Object)方法相等,则对两个对象中的每一个调用hashCode方法必须生成相同的整数结果。

您对hashCode()的实施不遵循此合同。你的第一步应该是解决这个问题。

其次,从Java 1.2(差不多20年前)开始,java提供了removeAll()方法,它完全符合你想要的第一部分:

// Given these 2 sets:
HashSet<MyObjDTO> oldSet = new HashSet<>();
HashSet<MyObjDTO> newSet = new HashSet<>();

HashSet<MyObjDTO> onlyInNew = new HashSet<>(newSet);
onlyInNew.removeAll(oldSet);
// similar for onlyInOld

对于第二部分,您需要创建一个Map来查找并获取该对象:

Map<MyObjDTO, MyObjDTO> map = new HashMap<>O;
oldSet.forEach(o -> map.put(o, o);

HashSet<MyObjDTO> updated = new HashSet<>(newSet);
updated.removeIf(o -> oldSet.contains(o) && o.getChecked()() != map.get(o).getChecked());

3
投票

一种方法是先使用地图,然后调整过滤条件:

Map<MyObjDTO, Boolean> map = newSet.stream()
    .collect(Collectors.toMap(Function.identity(), MyObjDTO::getChecked));

Stream<MyObjDTO> toUpdate = oldSet.stream()
    .filter(old -> newSet.contains(old) && old.getChecked() != map.get(old));

1
投票

在最后一步中,您依赖于DTO的equals()方法:

Stream<FonctionnaliteDTO> toUpdate = oldSet.stream().filter(newSet::contains);

该方法仅使用id field来确定对象相等性。 你不想那样做。 您想要过滤特定字段:checked

此外,您应该对两个集合的交集结果执行操作。

请注意,您应该使用Collection.retainAll()来计算两个集合之间的交集:

Set<MyObjDTO> set = ...
Set<MyObjDTO> setTwo = ...

set.retainAll(setTwo);

然后,您可以使用双循环过滤具有相同idchecked值的对象:for + iterator。

for (MyObjDTO dto : set){
   for (Iterator<MyObjDTO> it = set.iterator(); it.hasNext();){

      MyObjDTO otherDto = it.next();
      if (otherDto.getId().equals(dto.getId()) && 
           otherDto.getChecked() == dto.getChecked()){
         it.remove();
      }
   }
}

你可以用Stream做到这一点,但IHMO它可能不太可读。

© www.soinside.com 2019 - 2024. All rights reserved.