我的TreeSet具有以下比较器:
public class Obj {
public int id;
public String value;
public Obj(int id, String value) {
this.id = id;
this.value = value;
}
public String toString() {
return "(" + id + value + ")";
}
}
Obj obja = new Obj(1, "a");
Obj objb = new Obj(1, "b");
Obj objc = new Obj(2, "c");
Obj objd = new Obj(2, "a");
Set<Obj> set = new TreeSet<>((a, b) -> {
System.out.println("Comparing " + a + " and " + b);
int result = a.value.compareTo(b.value);
if (a.id == b.id) {
return 0;
}
return result == 0 ? Integer.compare(a.id, b.id) : result;
});
set.addAll(Arrays.asList(obja, objb, objc, objd));
System.out.println(set);
它打印出[(1a),(2c)],删除了重复项。
但是当我将最后一个Integer.compare
更改为Integer.compare(b.id, a.id)
时(即切换a和b的位置),它会打印出[(2a),(1a),(2c)]。显然,相同的ID 2出现了两次。
如何固定比较器,使其始终根据ID删除重复项,并根据值(升序)然后ID(降序)对有序集进行排序?
答案 0 :(得分:4)
您在问:
您如何修复比较器,使其始终根据id删除重复项,并根据值(升序)然后id(降序)对有序集合进行排序?
您希望比较器
Obj.id
删除重复项Obj.alue
和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);
我们来看看TreeSet
的{{3}}。它说:
请注意,集合[...]维护的顺序必须与
equals
一致 正确实现Set
接口。就是这样 因为Set
接口是根据equals
操作定义的, 但是TreeSet
实例使用其实例执行所有元素比较compareTo
(或比较)方法,因此两个元素被视为相等 从集合的角度来看,这种方法是相等的。
将根据比较器对集合进行排序,但还会使用比较器比较其元素是否相等。
据我所知,无法定义同时满足这两个要求的Comparator
。由于TreeSet
首先是Set
要求1)必须匹配。要达到要求2),您可以创建第二个TreeSet
:
Set<Obj> setByValueAndId = new TreeSet<>(sortingComparator);
setByValueAndId.addAll(setById);
或者,如果您不需要集合本身,而是以所需顺序处理元素,则可以使用Stream
:
Consumer<Obj> consumer = <your consumer>;
setById.stream().sorted(sortingComparator).forEach(consumer);
顺便说一句:
尽管可以根据给定的Stream
对Comparator
的元素进行排序,但没有distinct
的方法采用Comparator
来删除重复项。
编辑:
您有两个不同的任务:1.重复删除,2.排序。一个Comparator
不能解决两项任务。那有什么替代方法呢?
您可以覆盖equals
上的hashCode
和Obj
。然后可以使用HashSet
或Stream
删除重复项。
对于排序,您仍然需要Comparator
(如上所示)。根据{{1}} JavaDoc,仅将Comparable
用于排序将导致排序不等于“等于”。
由于Comparable
可以解决这两个任务,所以这是我的选择。首先,我们覆盖Stream
和hashCode
以通过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
的最后一行
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)
。
在第一次运行中,(1a)
大于(2a)
,因此将其与下一个条目(1a)
进行比较。这导致相等-找到重复项。
第二轮运行(2c)
小于(2a)
。因此,(1a)
将与下一个条目进行比较。但是(2a)
已经是最小的条目,并且没有上一个条目。因此,找不到(1a)
的重复项,并将其添加到集合中。
Q:您说一个比较器无法完成两项任务,而我的第一个比较器实际上正确地完成了两项任务。
是的-但仅适用于给定的示例。像我一样将(2a)
添加到集合中并运行您的代码。返回的排序集是:
Obj obj3a
这违反了您对以[(1a), (3a), (2c)]
降序的相等value
进行排序的要求。现在它以id
递增。运行我的代码,它返回正确的顺序,如上所示。
前段时间在id
上挣扎,我得到以下评论:“ ...这是一个很棒的练习,展示了手动比较器实现有多么棘手……”(JavaDoc )