你问的是:
如何修复比较器以始终根据 ids 删除重复项并根据值(升序)然后 id(降序)对有序集进行排序?
您希望比较器
- 根据删除重复项
Obj.id
- 对集合进行排序
Obj.value
and Obj.id
要求 1) 结果为
Function<Obj, Integer> byId = o -> o.id;
Set<Obj> setById = new TreeSet<>(Comparator.comparing(byId));
要求 2) 结果为
Function<Obj, String> byValue = o -> o.value;
Comparator<Obj> sortingComparator = Comparator.comparing(byValue).thenComparing(Comparator.comparing(byId).reversed());
Set<Obj> setByValueAndId = new TreeSet<>(sortingComparator);
让我们来看看JavaDoc https://docs.oracle.com/javase/10/docs/api/java/util/TreeSet.html of TreeSet
。它说:
请注意,集合 [...] 维护的顺序必须与equals
如果是为了
正确实施Set
界面。是这样的
因为Set
接口是根据以下定义的equals
手术,
但是一个TreeSet
实例使用其执行所有元素比较compareTo
(或比较)方法,因此两个元素被视为相等
通过这种方法,从集合的角度来看,它们是相等的。
该集合将根据比较器进行排序,但也会使用比较器比较其元素是否相等。
据我所知,没有办法定义Comparator
满足这两个要求。自从一个TreeSet
首先是aSet
要求 1) 必须匹配。要实现要求 2),您可以创建第二个TreeSet
:
Set<Obj> setByValueAndId = new TreeSet<>(sortingComparator);
setByValueAndId.addAll(setById);
或者,如果您不需要集合本身,但要按所需顺序处理元素,您可以使用Stream
:
Consumer<Obj> consumer = <your consumer>;
setById.stream().sorted(sortingComparator).forEach(consumer);
BTW:
虽然可以对 a 的元素进行排序Stream
根据给定的Comparator
没有distinct
方法采用Comparator
根据它删除重复项。
EDIT:
您有两个不同的任务:1.重复删除,2.排序。一Comparator
无法解决这两个任务。那么还有什么替代方案呢?
您可以覆盖equals
and hashCode
on Obj
。然后一个HashSet
or a Stream
可用于删除重复项。
对于排序,您仍然需要Comparator
(如上图所示)。实施Comparable
仅用于排序将导致排序与根据的“与等于”不一致Comparable
JavaDoc https://docs.oracle.com/javase/10/docs/api/java/lang/Comparable.html.
Since a Stream
可以解决这两个任务,这将是我的选择。首先我们重写hashCode
and equals
通过以下方式识别重复项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;
}
现在我们可以使用Stream
:
// instantiating one additional Obj and reusing those from the question
Obj obj3a = new Obj(3, "a");
// reusing sortingComparator from the code above
Set<Obj> 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
.
编辑(回答评论中的问题)
Q: 为什么它没有正确完成工作?
亲自看看吧。更改你的最后一行Comparator
就像下面这样
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)
and (1a)
进行比较。
在第一次运行中(2a)
大于(1a)
所以它与下一个条目进行比较(2c)
。这会导致相等 - 找到重复项。
在第二轮比赛中(2a)
小于(1a)
. Thus (2a)
将作为下一个与上一个条目进行比较。但(1a)
已经是最小的条目并且没有前一个条目。因此没有找到重复项(2a)
并将其添加到集合中。
Q: 您说一个比较器无法完成两项任务,我的第一个比较器实际上正确地完成了两项任务。
是的 - 但仅限于给定的示例。添加Obj obj3a
像我一样设置并运行你的代码。返回的排序集为:
[(1a), (3a), (2c)]
这违反了您对平等排序的要求value
s 下降id
。现在它正在上升id
。运行我的代码,它返回正确的顺序,如上所示。
Struggling with a Comparator
a time ago I got the following comment: "... it’s a great exercise, demonstrating how tricky manual comparator implementations can be ..." (source https://stackoverflow.com/questions/51519499/java-8-streams-find-element-and-add-it-to-the-start-of-the-new-list/51520311#comment90014480_51520311)