背景
最近有个需求,需要记录单据的状态轨迹,为了保证状态轨迹唯一且按时间排序,代码中使用的TreeSet来实现。某次偶然情况,发现出现两条相同的状态轨迹。
业务代码
状态节点
@EqualsAndHashCode(of = "status")
@Data
@AllArgsConstructor
public static class Node implements Comparable<Node>{
/**
* 节点状态
*/
private String status;
/**
* 节点时间
*/
private Date date;
@Override
public int compareTo(@NotNull Node o) {
// 状态相同,证明是Node是相等的
if (this.status.equals(o.status)) {
return 0;
}
// 状态不同,比较时间
return this.date.compareTo(o.getDate());
}
}
复制代码
TreeSet去重
public static void main(String[] args) throws InterruptedException {
Node node1 = new Node("ACCEPT", new Date(1643164565275L));
Node node2 = new Node("DISPATCH_CAR", new Date(1643167154199L));
Node node3 = new Node("DELIVERY", new Date(1643167155111L));
TreeSet<Node> treeSet = new TreeSet<>(Lists.newArrayList(node1, node2, node3));
Node node4 = new Node("ACCEPT", new Date());
treeSet.add(node4);
System.out.println(treeSet);
}
复制代码
如上代码所示,Node这个类是使用status字段来判断相等的,排序是使用了date字段。这个时候在使用TreeSet时,期望是能够去重的,但是上述代码实际上出现了两个status=ACCEPT的节点。
原因
TreeSet使用TreeMap来实现,如下为TreeMap#put代码。TreeMap直接使用compareTo方法来比较大小,并且因为是Tree,所以不会每个节点都比较,而是只会比较左右子树的其中一部分。 而业务中,我们使用status做相同的判断,而compareTo则使用date来排序,所以就出现了重复状态节点的情况。
public V put(K key, V value) {
// 其他省略
if (key == null)
throw new NullPointerException();
@SuppressWarnings("unchecked")
Comparable<? super K> k = (Comparable<? super K>) key;
do {
parent = t;
cmp = k.compareTo(t.key);
if (cmp < 0)
t = t.left;
else if (cmp > 0)
t = t.right;
else
return t.setValue(value);
} while (t != null);
// 其他省略
}
复制代码
解决方案
使用HashSet去重,最后转成List,再对List进行排序。