保持可变对象始终在TreeSet中排序

时间:2011-11-06 20:08:09

标签: java treeset

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

6 个答案:

答案 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)

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

    }

}