您在询问:
如何固定比较器,使其始终根据ID删除重复项,并根据值(升序)然后ID(降序)对有序集进行排序?
您想要比较器
>根据Obj.id删除重复项
>按Obj.alue和Obj.id对集合进行排序
要求1)导致
Function byId = o -> o.id;
Set setById = new TreeSet<>(Comparator.comparing(byId));
要求2)导致
Function byValue = o -> o.value;
Comparator sortingComparator = Comparator.comparing(byValue).thenComparing(Comparator.comparing(byId).reversed());
Set setByValueAndId = new TreeSet<>(sortingComparator);
让我们看一下TreeSet的JavaDoc.它说:
Note that the ordering maintained by a set […] must be consistent with equals if it is to
correctly implement the Set interface. This is so
because the Set interface is defined in terms of the equals operation,
but a TreeSet instance performs all element comparisons using its
compareTo (or compare) method, so two elements that are deemed equal
by this method are, from the standpoint of the set, equal.
该集合将根据比较器进行排序,但还使用比较器比较其元素是否相等.
据我所知,无法定义同时满足这两个要求的比较器.由于TreeSet首先位于Set要求1)中,因此必须匹配.为了达到要求2),您可以创建另一个TreeSet:
Set setByValueAndId = new TreeSet<>(sortingComparator);
setByValueAndId.addAll(setById);
或者,如果您不需要集合本身,而是以所需顺序处理元素,则可以使用Stream:
Consumer consumer = ;
setById.stream().sorted(sortingComparator).forEach(consumer);
顺便说一句:
尽管可以根据给定的Comparator对Stream的元素进行排序,但没有一种采用Comparator来根据其删除重复项的独特方法.
编辑:
您有两个不同的任务:1.重复删除,2.排序.一个比较器不能解决两个任务.那有什么选择呢?
您可以在Obj上覆盖equals和hashCode.然后,可以使用HashSet或Stream删除重复项.
对于排序,您仍然需要比较器(如上所示).根据可比较JavaDoc,仅将可比较对象用于排序将导致排序不等于等式.
由于Stream可以解决这两个任务,因此这是我的选择.首先,我们覆盖hashCode并等于ID以标识重复项:
public int hashCode() {
return Integer.hashCode(id);
}
@Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
Obj other = (Obj) obj;
if (id != other.id)
return false;
return true;
}
现在我们可以使用流:
// instantiating one additional Obj and reusing those from the question
Obj obj3a = new Obj(3, "a");
// reusing sortingComparator from the code above
Set set = Stream.of(obja, objb, objc, objd, obj3a)
.distinct()
.sorted(sortingComparator)
.collect(Collectors.toCollection(LinkedHashSet::new));
System.out.println(set); // [(3a), (1a), (2c)]
返回的LinkedHashSet具有Set的语义,但它也保留sortingComparator的顺序.
编辑(回答评论中的问题)
问:为什么它不能正确完成工作?
自己看看.如下更改您的比较器的最后一行
int r = result == 0 ? Integer.compare(a.id, b.id) : result;
System.out.println(String.format("a: %s / b: %s / result: %s -> %s", a.id, b.id, result, r));
return r;
运行一次代码,然后切换Integer.compare的操作数.开关导致不同的比较路径.区别在于何时比较(2a)和(1a).
在第一轮中(2a)大于(1a),因此将其与下一个条目(2c)进行比较.这导致相等-找到重复项.
在第二轮中(2a)小于(1a).因此,(2a)将与下一个条目进行比较.但是(1a)已经是最小的条目,并且没有上一个条目.因此,找不到(2a)的重复项并将其添加到集合中.
问:您说一个比较器不能完成两项任务,而我的第一个比较器实际上完成了两项任务.
是的-但仅适用于给定的示例.像我一样将Obj obj3a添加到集合中并运行您的代码.返回的排序集为:
[(1a), (3a), (2c)]
这违反了您对按id降序的相等值进行排序的要求.现在它通过id升序.运行我的代码,它返回正确的顺序,如上所示.
不久前与比较器作斗争时,我收到以下评论:“ …这是一个很棒的练习,展示了手动比较器实现的技巧如何……”(source)