使用 Java 比较器按另一个列表对对象列表进行排序

Sort object List by another List using Java Comparators

有两个输入列表如下:

inputA = [
            {
               name: "A",
               age: 20
            }, 
            {
               name: "B",
               age: 30
            },
            {  name: "C",
               age: 25
            },
            {  name: "D",
               age: 28
            }
          ]

inputB = ["D", "B"]

我的首选输出列表必须如下:

expectedOutput = [
            {
               name: "D",
               age: 28
            }, 
            {
               name: "B",
               age: 30
            },
            {  name: "A",
               age: 20
            },
            {  name: "C",
               age: 25
            }
          ]

到目前为止我所做的如下所示:

AtomicInteger count = new AtomicInteger();
Collections.sort(inputA, Comparator
    .comparing(a -> 
    if (inputB.indexOf(a.getName()) > -1) {
        return -1;
    }
    else {
        return count.incrementAndGet();
    })
    .thenComparingInt(a -> a.getAge()));

我得到的输出如下

actualOutput = [
            {
               name: "D",
               age: 28
            }, 
            {
               name: "B",
               age: 30
            },
            {  name: "C",
               age: 25
            },
            {  name: "A",
               age: 20
            }
          ]

问题出在列表 inputB 中没有其名称的元素。该订单在 inputA 中没有原始订单。为了保持原来的顺序 { name: "A", age: 20 } 应该在 { name: "C", age: 25 }

之前

如何在使用比较器链接策略时解决这个问题?

更新 排序逻辑是,如果 inputA 具有名称与 inputB 列表相同的对象,则这些元素应位于 inputA 的顶部,然后这些元素必须按其年龄排序,同时保持 inputA 中其他元素的原始顺序不存在于 inputB

这不可能是重复的,因为这个问题试图比较两个列表,并根据第一个列表中对象的 属性 对公共元素进行排序,同时将其余元素保留在它们的列表中原始订单。

您的代码中的问题是第二个比较器在 inputA 中的所有项目上被激活。这意味着,即使不在 inputB 中的项目也会被排序,打破 inputA 的原始顺序。你可以这样避免它:

Collections.sort(inputA, Comparator.comparing(a ->
{
    if (inputB.indexOf(a.getName()) > -1)
    {
        return 0;
    }
    return 1;
})
.thenComparingInt(a -> 
{
   if (inputB.indexOf(a.getName()) > -1)
   {
       return a.getAge();
   }
   return Integer.MAX_VALUE;
}));

但是,如果 inputB 未排序,我不确定是否需要特别调用 IndexOf() 两次。可能值得查看生成的字节码以查看其是否得到优化。

有一件事是,使用计数对比较没有任何影响。您只需要在比较操作中 return -1、0 或 1。因此,对计数执行线程安全增量没有任何价值。

另一方面,如果可以的话,在 class 中引入一个 priority 字段来定义 inputA 中的对象。然后你可以解决多次调用 indexOf() 的问题,例如:

Collections.sort(inputA, Comparator.comparing(a ->
{
   if (inputB.indexOf(a.getName()) > -1)
   {
       a.setPriority(a.getAge());
       return 0;
   }

   a.setPriority(Integer.MAX_VALUE);
   return 1;
})
.thenComparingInt(a -> a.getPriority()));

如我所见,如果名称包含在 inputB 列表中,则需要按年龄对元素进行排序,如果 [=] 列表中不包含其余元素,则保持原样11=] 列表。按年龄排序的元素应该出现在结果的顶部,而未排序的元素应该出现在结果的底部。

如果这是你需要做的,你可以使用 Comparator.comparingInt 并让它 return 一个整数,它可以是年龄(对于第一种情况)或 Integer.MAX_VALUE (对于另一种情况)。

您应该优化 inputB 的检查,以便它更快。为此,您可以从 inputB.

创建一个 HashSet

这是代码:

Set<String> set = new HashSet<>(inputB);

Collections.sort(inputA, Comparator.comparingInt(a -> set.contains(a.getName()) ? 
                                                      a.getAge() : 
                                                      Integer.MAX_VALUE));

这有效,只要您的年龄不等于 Integer.MAX_VALUE

这个想法是你总是按年龄比较,但如果一个元素不属于 inputB,你把年龄变成 Integer.MAX_VALUE。这会产生两个效果:第一,它会让inputB中不包含的元素出现在底部;其次,正如您总是 return Integer.MAX_VALUEinputA 列表的顺序被保留,因为 Collections.sort 实现了 稳定排序