13

我注意到,如果稍后更改对象属性值,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 似乎不再按排序顺序排列。为什么会这样?以及如何始终保持排序?

4

6 回答 6

13

为什么会这样?

因为该集合无法监视其所有对象的更改……它怎么能做到这一点?!

出现同样的问题HashSetsHashSet当 a持有对象时,您不能更改影响对象哈希码的值。

以及如何始终保持排序?

您通常从集合中移除元素,对其进行修改,然后重新插入。换句话说,改变

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在每次修改对象时调用列表。

于 2011-11-06T20:13:39.297 回答
9

你可以利用观察者模式。让你的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就会“重新排序”自己。

于 2011-11-06T20:31:19.847 回答
1

这是 Maps 和 Sets 的一般问题。这些值是在插入时使用 hashCode/equals/compare 插入的,如果这些方法所基于的值发生变化,那么结构可能会搞砸。

一种方法是从集合中删除该项目并在更改值后重新添加它。那么这将是正确的。

于 2011-11-06T20:14:43.690 回答
0

Glazed Lists 可以提供帮助:http ://www.glazedlists.com/

我将它用于它的 EventList 并且没有尝试过排序。但在他们的主页上,他们列出了主要功能:

实时排序意味着您的表格在数据更改时保持排序。

于 2015-02-04T16:30:59.817 回答
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;
        }

    }

}
于 2016-07-27T16:00:15.403 回答
0

如果您的问题是迭代顺序,并且您不想使用TreeSet( headSet()etc.) 的额外功能,请使用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();
        }

    }

}
于 2016-08-10T20:14:07.990 回答