java 比较两个列表并获得差异

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/42026164/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-11-03 06:17:47  来源:igfitidea点击:

Comparing two lists and getting differences

javaspring-bootjava-8

提问by mirzak

I have two lists. They contain objects of different types, but both types contain id and name, and id is what I am comparing on. List one is fetched from DB, and list two is sent from frontend.

我有两个清单。它们包含不同类型的对象,但两种类型都包含 id 和 name,而 id 是我比较的对象。列表一是从数据库中获取的,列表二是从前端发送的。

What I need to do is loop through them and find which list item is newly added and which one was deleted.

我需要做的是遍历它们并找到新添加的列表项和删除的列表项。

I was able to do it, but the problem is that it look ugly.

我能够做到,但问题是它看起来很难看。

Let's say I have a object that is called NameDTO which can have id and name. List two is filled with that type of objects.

假设我有一个名为 NameDTO 的对象,它可以有 id 和 name。列表二充满了那种类型的对象。

This is how I did it:

我是这样做的:

final ArrayList<NamedDTO> added = new ArrayList<>();
final ArrayList<NamedDTO> removed = new ArrayList<>();

for(NamedDTO listTwoObject : listTwo) {
   boolean contained = false;
   for(SomeObject listOneObject : listOne) {
       if(listTwoObject.getId().equals(listOneObject.getId()) {
           contained = true;
       }
   }
   if(!contained) {
      added.add(listTwoObject);
   }
}

for(SomeObject listOneObject : listOne) {
   boolean contained = false;
   for(NamedDTO listTwoObject : listTwo) {
       if(listTwoObject.getId().equals(listOneObject.getId()) {
           contained = true;
       }
   }
   if(!contained) {
      removed.add(new NamedDTO(listOneObject.getId(), listOneObject.getName()));
  }
}

This works, I have tested it. Are there better solutions? I was thinking of using Sets so I can compare them, Is there a downside to that ?

这有效,我已经测试过了。有更好的解决方案吗?我正在考虑使用 Sets 以便我可以比较它们,这有什么缺点吗?

回答by Atais

If I understand correctly, this is the example scenario:

如果我理解正确,这是示例场景:

  • listOne [datab] items: [A, B, C, D]
  • listTwo [front] items: [B, C, D, E, F]
  • listOne [datab] 项目: [A, B, C, D]
  • 列出两个 [前面] 项: [B, C, D, E, F]

and what you need to get as an effect is:

你需要得到的效果是:

  • added: [E, F]
  • deleted: [A]
  • 添加: [E, F]
  • 删除: [A]


First thing first, I would use some type adapter or extend the different types from one common class and overridethe equalsmethod so you can match them by idand name

第一件事,第一,我会使用某种类型的适配器或从一个普通类扩展的不同类型和不同overrideequals方法,这样你就可以通过匹配它们idname

Secondly, this is very easy operations on sets (you could use set's but list are fine too). I recommend using a library: https://commons.apache.org/proper/commons-collections/apidocs/org/apache/commons/collections4/CollectionUtils.html

其次,这是对集合的非常简单的操作(您可以使用集合,但列表也可以)。我建议使用一个库:https: //commons.apache.org/proper/commons-collections/apidocs/org/apache/commons/collections4/CollectionUtils.html

And now basically:

现在基本上:

  • added is listTwo - listOne
  • deleted is listOne - listTwo
  • 添加的是 listTwo - listOne
  • 删除的是 listOne - listTwo

and using java code:

并使用java代码:

  • added: CollectionUtils.removeAll(listTwo, listOne)
  • deleted: CollectionUtils.removeAll(listOne, listTwo)
  • 添加: CollectionUtils.removeAll(listTwo, listOne)
  • 删除: CollectionUtils.removeAll(listOne, listTwo)


Otherwise, all collections implementing Collection(Java Docs) also has removeAllmethod, which you can use.

否则,所有实现Collection( Java Docs) 的集合也有removeAll方法,您可以使用它。

回答by Kamil Banaszczyk

I propose solution using java 8 streams:

我提出使用 java 8 流的解决方案:

    ArrayList<ObjOne> list = new ArrayList<>(Arrays.asList(new ObjOne("1","1"),new ObjOne("3","3"),new ObjOne("2","2")));
    ArrayList<ObjTwo> list2 = new ArrayList<>(Arrays.asList(new ObjTwo("1","1"),new ObjTwo("3","3"),new ObjTwo("4","4")));

    List<ObjOne> removed = list.stream().filter(o1 -> list2.stream().noneMatch(o2 -> o2.getId().equals(o1.getId())))
            .collect(Collectors.toList());
    System.out.print("added ");
    removed.forEach(System.out::println);

    List<ObjTwo> added = list2.stream().filter(o1 -> list.stream().noneMatch(o2 -> o2.getId().equals(o1.getId())))
             .collect(Collectors.toList());

    System.out.print("removed ");
    added.forEach(System.out::println);

This is basically your solution but implemented using streams, which will make your code shorter and easer to read

这基本上是您的解决方案,但使用流实现,这将使您的代码更短且更易于阅读

回答by Holger

This nested list processing is not only ugly, it's inefficient. You are always better off storing the IDs of one list into a Setallowing efficient lookup, then process the other list utilizing the Set. This way, you're not performing list1.size()times list2.size()operations, but list1.size()plus list2.size()operations, which is a significant difference for larger lists. Then, since both operations are fundamentally the same, it's worth abstracting them into a method:

这种嵌套列表处理不仅丑陋,而且效率低下。最好将一个列表的 ID 存储到Set允许有效的查找中,然后使用Set. 这样,您不是在执行list1.size()时间list2.size()操作,而是执行list1.size()list2.size()操作,这对于较大的列表来说是一个显着的区别。然后,由于这两个操作在本质上是相同的,因此值得将它们抽象为一个方法:

public static <A,B,R,ID> List<R> extract(
    List<A> l1, List<B> l2, Function<A,ID> aID, Function<B,ID> bID, Function<A,R> r) {

    Set<ID> b=l2.stream().map(bID).collect(Collectors.toSet());
    return l1.stream().filter(a -> !b.contains(aID.apply(a)))
             .map(r).collect(Collectors.toList());
}

This method can be used as

这种方法可以用作

List<NamedDTO> added   = extract(listTwo, listOne, NamedDTO::getId, SomeObject::getId,
                                 Function.identity());
List<NamedDTO> removed = extract(listOne, listTwo, SomeObject::getId, NamedDTO::getId,
                                 so -> new NamedDTO(so.getId(), so.getName()));

Since swapping the two lists requires the helper method to be independent from the element types, it expects functions for accessing the id property, which can be specified via method references. Then, a function describing the result element is required, which is an identity function in one case (just getting the NamedDTO) and a lambda expression constructing a NamedDTOfrom SomeObjectin the other.

由于交换两个列表需要 helper 方法独立于元素类型,因此它需要访问 id 属性的函数,可以通过方法引用指定。然后,需要一个描述结果元素的函数,它在一种情况下是一个恒等函数(只是得到NamedDTO),而在另一种情况下是一个构造NamedDTOfrom的 lambda 表达式SomeObject

The operation itself is as straight-forward as described above, iterate over one list, map to the id and collect into a Set, then, iterate over the other list, keep only elements whose id is not in the set, map to the result type and collect into a List.

操作本身和上面描述的一样简单,遍历一个列表,映射到 id 并收集到 a Set,然后遍历另一个列表,只保留 id 不在集合中的元素,映射到结果类型并收集到一个List.

回答by Eugene

If these id's are unique, you could put them in a HashSet and find thus the ids you are interested in:

如果这些 id 是唯一的,您可以将它们放在 HashSet 中,从而找到您感兴趣的 id:

    Set<Integer> uiList = Stream.of(new FromUI(1, "db-one"), new FromUI(2, "db-two"), new FromUI(3, "db-three"))
            .map(FromUI::getId)
            .collect(Collectors.toCollection(HashSet::new));
    Set<Integer> dbList = Stream.of(new FromDB(3, "ui-one"), new FromDB(5, "ui-five"))
            .map(FromDB::getId)
            .collect(Collectors.toCollection(HashSet::new));

    uiList.removeIf(dbList::remove);

added/uiSet :   [1,2]
removed/dbSet : [5]

I've created FromUIand FromDBclasses with a constructor that takes the id and name as input.

我创建FromUIFromDB班级,一个构造函数的ID和名称输入。

I also assume that if an element is contained in the uiSet, but not in dbSetis has been added and the other way around.

我还假设如果一个元素包含在 中uiSet,但不包含在 中,dbSet则已添加,反之亦然。