英文:
Is it ok to change a value that is used in compareTo in a TreeSet?
问题
以下是翻译好的部分:
我有这个简单的 Pojo:
public static class Pojo implements Comparable<Pojo> {
Integer value;
@Override
public int compareTo(Pojo o) {
return value.compareTo(o.value);
}
// 省略了 getters 和 setters
}
在一个 TreeSet
中改变 Pojo
的 value
字段是否可以?如果这会破坏 TreeSet
中的排序,我并不介意。
我很好奇,因为在实例在 HashMap
中时改变用于 hashCode()
或 equals()
的 value
是不明智的。
英文:
I have this simple Pojo:
public static class Pojo implements Comparable<Pojo> {
Integer value;
@Override
public int compareTo(Pojo o) {
return value.compareTo(o.value);
}
// getters and setter ommited
}
Is it ok to change value
of a Pojo
while it's in a TreeSet
? I don't mind if this breaks the sorting in the TreeSet
.
I'm curious because changing value
used in a hashCode()
or equas()
while the instance is in a HashMap
is not a good idea.
答案1
得分: 5
如果我们改变影响元素顺序的属性,就会违反TreeSet
的契约。
根据TreeSet
的文档,它
> ... 对基本操作(add
、remove
和contains
)提供了确保的O(log n)时间复杂度。
TreeSet
可以通过保持集合有序来保证这些时间成本。然而,如果我们改变元素的属性,从而改变了集合内部的顺序,就会打破这个契约。TreeSet
并不会不断地“监视”所有元素的变化。此外,一次变化可能会触发重新排序,这将产生n * log(n)
的成本(其中n
是TreeSet
当前的大小)。
我们可以通过以下代码来展示这种形式的契约违反效果:
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
import java.util.TreeSet;
class Ideone {
public static void main(String[] args) {
Foo fooOne = Foo.of(1);
Foo fooTwo = Foo.of(2);
Foo fooThree = Foo.of(3);
TreeSet<Foo> set = new TreeSet<>();
set.addAll(List.of(fooOne, fooTwo, fooThree));
System.out.println(set.contains(fooOne));
fooOne.setValue(4);
System.out.println(set.contains(fooOne));
System.out.println(new ArrayList<>(set).contains(fooOne));
}
}
class Foo implements Comparable<Foo> {
private int value;
private Foo(int value) {
this.setValue(value);
}
public static Foo of(int value) {
return new Foo(value);
}
public int getValue() {
return value;
}
public Foo setValue(int value) {
this.value = value;
return this;
}
@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (o == null || getClass() != o.getClass()) {
return false;
}
Foo foo = (Foo) o;
return getValue() == foo.getValue();
}
@Override
public int hashCode() {
return Objects.hash(value);
}
@Override
public int compareTo(Foo that) {
return getValue() - that.getValue();
}
}
如果TreeSet
会在元素属性变化时重新排序,这三行代码都应该显示为true
。然而,由于我们将fooOne
的value
从1
更改为4
(因此预期它将成为集合中的最后一个元素),第二个contains(fooOne)
将返回false
,因为TreeSet
不再排序。然而,将TreeSet
转换为ArrayList
并在ArrayList
中搜索会产生预期的结果,因为ArrayList
不会对元素顺序做出任何假设。
如果基本操作的O(log n)时间成本对使用情况不是必要的,我建议使用不依赖于元素顺序的不同数据结构(例如List
)。然而,请记住,这会导致(至少部分)基本操作的时间成本增加。
编辑:
正如在评论中由@Scratte提到的,我们甚至可以通过将(修改后,但仍与==
相同)的fooOne
添加到TreeSet
中,在更基本的层面上破坏TreeSet
,从而将其大小增加一:
class Ideone {
public static void main(String[] args) {
Foo fooOne = Foo.of(1);
Foo fooTwo = Foo.of(2);
Foo fooThree = Foo.of(3);
TreeSet<Foo> set = new TreeSet<>();
set.addAll(List.of(fooOne, fooTwo, fooThree));
System.out.println(set.size());
fooOne.setValue(4);
set.add(fooOne);
System.out.println(set.size());
}
}
英文:
If we change attributes that influence the element order, we violate contracts of TreeSet
.
As per TreeSet
's documentation, it
> ... provides guaranteed log(n) time cost for the basic operations (add
, remove
and contains
).
The TreeSet
can guarantee those time costs by keeping the set ordered. If we, however, change attributes of elements, that would in turn change the ordering within the set, we break this contract. The TreeSet
does not constantly "monitor" all elements for changes. Furthermore, a change could trigger a reorder, which would have costs of n * log(n)
(with n
being the current size of the TreeSet
).
We can make the effects of this form of contract violation visible with the following code:
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
import java.util.TreeSet;
class Ideone {
public static void main(String[] args) {
Foo fooOne = Foo.of(1);
Foo fooTwo = Foo.of(2);
Foo fooThree = Foo.of(3);
TreeSet<Foo> set = new TreeSet<>();
set.addAll(List.of(fooOne, fooTwo, fooThree));
System.out.println(set.contains(fooOne));
fooOne.setValue(4);
System.out.println(set.contains(fooOne));
System.out.println(new ArrayList<>(set).contains(fooOne));
}
}
class Foo implements Comparable<Foo> {
private int value;
private Foo(int value) {
this.setValue(value);
}
public static Foo of(int value) {
return new Foo(value);
}
public int getValue() {
return value;
}
public Foo setValue(int value) {
this.value = value;
return this;
}
@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (o == null || getClass() != o.getClass()) {
return false;
}
Foo foo = (Foo) o;
return getValue() == foo.getValue();
}
@Override
public int hashCode() {
return Objects.hash(value);
}
@Override
public int compareTo(Foo that) {
return getValue() - that.getValue();
}
}
If the TreeSet
would reorder on element attribute changes, all three lines should show true
. However, since we changed fooOne
's value
from 1
to 4
(hence it would be expected to be the last element in the set), the second contains(fooOne)
will return false
since the TreeSet
is no longer sorted. Converting the TreeSet
into an ArrayList
and searching within the ArrayList
, however, yields the expected result since the ArrayList
does not make any assumption about element order.
If the guaranteed log(n) time cost for basic operation is not essential to the use case, I would suggest using a different data structure (e.g. a List
) that does not rely on element ordering. Keep in mind, however, that this comes at a higher time cost for (at least some of the) basic operations.
EDIT:
As was mentioned by @Scratte in the comments, we can even break the TreeSet
on a more fundamental level by adding the (modified, but still identical wrt. ==
) fooOne
to the TreeSet
, increasing its size by one:
class Ideone {
public static void main(String[] args) {
Foo fooOne = Foo.of(1);
Foo fooTwo = Foo.of(2);
Foo fooThree = Foo.of(3);
TreeSet<Foo> set = new TreeSet<>();
set.addAll(List.of(fooOne, fooTwo, fooThree));
System.out.println(set.size());
fooOne.setValue(4);
set.add(fooOne);
System.out.println(set.size());
}
}
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论