我注意到,如果稍后更改对象属性值,TreeSet不会按可变对象保持排序顺序。例如,
public class Wrap {
static TreeSet<Student> ts = new TreeSet<Student>(new Comparator<Student>(){
@Override
public int compare(Student o1, Student o2) {
return o1.age - o2.age;
}
});
public static void main(String []args){
Student s = new Student(10);
ts.add(s);
ts.add(new Student(50));
ts.add(new Student(30));
ts.add(new Student(15));
System.out.println(ts);
s.age = 24; //Here I change the age of a student in the TreeSet
System.out.println(ts);
}
}
class Student{
int age;
Student(int age){
this.age = age;
}
@Override
public String toString() {
return "Student [age=" + age + "]";
}
}
输出结果为:
[Student [age=10], Student [age=15], Student [age=30], Student [age=50]]
[Student [age=24], Student [age=15], Student [age=30], Student [age=50]]
在我更改特定学生的年龄,然后打印TreeSet后,Set似乎不再按排序顺序排列。为什么会这样?以及如何保持它总是排序?
答案 0 :(得分:11)
为什么会这样?
因为该集不能监视所有对象的更改......它将如何做到这一点?!
HashSets
出现同样的问题。当HashSet
持有对象时,您无法更改影响对象哈希码的值。
以及如何对其进行排序?
通常从集合中删除元素,修改它,然后重新插入。换句话说,改变
s.age = 24; //Here I change the age of a student in the TreeSet
到
ts.remove(s);
s.age = 24; //Here I change the age of a student in the TreeSet
ts.add(s);
您还可以使用例如列表,并在每次修改对象时在列表上调用Collections.sort
。
答案 1 :(得分:7)
您可以使用observer pattern。让您的TreeSet
实施Observer
并让Student
延长Observable
。您需要做的唯一更改是通过封装隐藏age
字段,以便您可以对更改进行更多内部控制。
这是一个启动示例:
public class ObservableTreeSet<O extends Observable> extends TreeSet<O> implements Observer {
public ObservableTreeSet(Comparator<O> comparator) {
super(comparator);
}
@Override
public boolean add(O element) {
element.addObserver(this);
return super.add(element);
}
@Override
@SuppressWarnings("unchecked")
public void update(Observable element, Object arg) {
remove(element);
add((O) element);
}
}
和
public class Student extends Observable {
private int age;
Student(int age) {
this.age = age;
}
public int getAge() {
return age;
}
public void setAge(int age) {
if (this.age != age) {
setChanged();
}
this.age = age;
if (hasChanged()) {
notifyObservers();
}
}
@Override
public String toString() {
return "Student [age=" + age + "]";
}
}
现在执行new ObservableTreeSet
而不是new TreeSet
。
static TreeSet<Student> ts = new ObservableTreeSet<Student>(new Comparator<Student>() {
@Override
public int compare(Student o1, Student o2) {
return o1.getAge() - o2.getAge();
}
});
乍一看是丑陋的,但最终主要代码没有变化。只需执行s.setAge(24)
,TreeSet
将&#34;重新排序&#34;本身。
答案 2 :(得分:1)
这是地图和集的一般问题。在插入时使用hashCode / equals / compare插入值,如果这些方法所基于的值发生变化,那么结构就会搞砸。
一种方法是从集合中删除该项目,并在更改值后重新添加该项目。那么这是正确的。
答案 3 :(得分:0)
上釉清单可以提供帮助:http://www.glazedlists.com/
我将它用于EventList并且没有尝试排序。但在他们的主页上,他们列出了主要特征:
实时排序表示您的表会随着数据的变化而保持排序。
答案 4 :(得分:0)
通常,最好手动保持已排序的Set
/ Map
一致(请参阅@aioobe提到的策略)。
但是,有时这不是一种选择。在这些情况下,我们可以尝试这个:
if (treeSet.contains(item)) {
treeSet.remove(item);
treeSet.add(item);
}
或使用地图:
if (treeMap.containsKey(key)) {
Value value = treeMap.get(key);
treeMap.remove(key);
treeMap.put(key, value);
}
但这不会正常工作,因为即使containsKey
也会导致错误的结果。
那么我们可以用脏地图做什么呢? 如何在不重建整个地图的情况下刷新单个键?这是一个解决此问题的实用程序类(可以很容易地转换为句柄集):
public class MapUtil {
/**
* Rearranges a mutable key in a (potentially sorted) map
*
* @param map
* @param key
*/
public static <K, V> void refreshItem(Map<K, V> map, K key) {
SearchResult<K, V> result = MapUtil.searchMutableKey(map, key);
if (result.found) {
result.iterator.remove();
map.put(key, result.value);
}
}
/**
* Searches a mutable key in a (potentially sorted) map
*
* Warning: currently this method uses equals() to check equality.
* The returned object contains three fields:
* - `found`: true iff the key found
* - `value`: the value under the key or null if `key` not found
* - `iterator`: an iterator pointed to the key or null if `key` not found
*
* @param map
* @param key
* @return
*/
public static <K, V> SearchResult<K, V> searchMutableKey(Map<K, V> map, K key) {
Iterator<Map.Entry<K, V>> entryIterator = map.entrySet().iterator();
while (entryIterator.hasNext()) {
Map.Entry<K, V> entry = entryIterator.next();
if (key.equals(entry.getKey())) {
return new SearchResult<K, V>(true, entry.getValue(), entryIterator);
}
}
return new SearchResult<K, V>(false, null, null);
}
public static class SearchResult<K, V> {
final public boolean found;
final public V value;
final public Iterator<Map.Entry<K, V>> iterator;
public SearchResult(boolean found, V value, Iterator<Map.Entry<K, V>> iterator) {
this.found = found;
this.value = value;
this.iterator = iterator;
}
}
}
答案 5 :(得分:0)
如果您的问题是迭代顺序,并且您不想使用TreeSet
(headSet()
等)的额外功能,则使用HashSet
和自定义迭代器。此外,您的例子存在一个主要问题:两个同龄的学生(通常会发生这种情况)会发生冲突。
可能的解决方案:
public class Main {
public static void main(final String[] args) {
MagicSet<Student> ts = new MagicSet<Student>(new Comparator<Student>() {
@Override
public int compare(Student student1, Student student2) {
return student1.age - student2.age;
}
});
Student s = new Student(10);
ts.add(s);
ts.add(new Student(50));
ts.add(new Student(30));
ts.add(new Student(15));
System.out.println(ts); // 10, 15, 30, 50
s.age = 24;
System.out.println(ts); // 15, 24, 30, 50
}
public static class Student {
public int age;
public Student(int age) {
this.age = age;
}
@Override
public String toString() {
return "Student [age=" + age + "]";
}
}
public static class MagicSet<T> extends HashSet<T> {
private static final long serialVersionUID = -2736789057225925894L;
private final Comparator<T> comparator;
public MagicSet(Comparator<T> comparator) {
this.comparator = comparator;
}
@Override
public Iterator<T> iterator() {
List<T> sortedList = new ArrayList<T>();
Iterator<T> superIterator = super.iterator();
while (superIterator.hasNext()) {
sortedList.add(superIterator.next());
}
Collections.sort(sortedList, comparator);
return sortedList.iterator();
}
}
}