簡體   English   中英

java TreeSet:比較和相等

[英]java TreeSet: comparing and equality

我想要使​​用屬性'sort_1'排序對象列表。 但是當我想刪除時,我希望它使用屬性'id'。 以下代碼表示問題。

package javaapplication1;

import java.util.TreeSet;

public class MyObj implements Comparable<MyObj> {
    public long sort_1;
    public long id;

    public MyObj(long sort, long id) {
        this.sort_1=sort;
        this.id=id;
    }

    @Override
    public int compareTo(MyObj other) {        
        int ret = Long.compare(sort_1, other.sort_1);               
        return ret;
    }    

    public String toString() {
        return id+":"+sort_1;
    }

    public static void main(String[] args) {
        TreeSet<MyObj> lst=new TreeSet<MyObj>();

                MyObj o1 = new MyObj(99,1);
        MyObj o2 = new MyObj(11,9);

        lst.add(o1);
        lst.add(o2);    

        System.out.println(lst);

                MyObj o3 = new MyObj(1234, 1);
                //remove myObje with id 1
                boolean remove=lst.remove(o3);

                System.out.println(lst);
    }

}

此代碼的輸出是:

[9:11, 1:99]
[9:11, 1:99]

我需要對列表進行排序,因為我對列表進行了大量添加。 我不想明確使用任何'sort'方法。 我有什么選擇?

編輯:

我的要求是:具有'id'的對象是唯一的,但是可以存在具有重復'sort'值的對象。

我偶然發現了昨天的情況。 這似乎是TreeMap實現的工件(TreeSet用於存儲其條目)。

TreeMap使用二叉搜索樹來存儲鍵/值對,但它只使用給定的Comparator(或者如果鍵類實現Comparable,則使用compare函數)來檢查相等性,正如您在此代碼中看到的exxcerpt:

final Entry<K,V> getEntry(Object key) {
    // Offload comparator-based version for sake of performance
    if (comparator != null)
        return getEntryUsingComparator(key);
    if (key == null)
        throw new NullPointerException();
    @SuppressWarnings("unchecked")
        Comparable<? super K> k = (Comparable<? super K>) key;
    Entry<K,V> p = root;
    while (p != null) {
        int cmp = k.compareTo(p.key);
        if (cmp < 0)
            p = p.left;
        else if (cmp > 0)
            p = p.right;
        else
            return p;
    }
    return null;
}

我幾乎稱之為(不是真正可修復的)錯誤,因為Comparable接口JavaDoc明確表示使用compareTo函數返回0並不一定意味着“相等”:

強烈建議,但並非嚴格要求(x.compareTo(y)== 0)==(x.equals(y))。

您將無法按照您希望的方式在TreeSet中存儲內容。 我建議使用普通的HashMapLinkedHashMap ,然后在需要使用Collections.sort對其進行排序時對輸​​出進行排序。

除此之外,我總是覺得實現Comparable接口很奇怪。 大多數事情並沒有真正具有明顯的“自然”順序。 有時候這會導致奇怪的錯誤(比如這個!),所以我通常只在需要使用自定義比較器時才進行排序。 Java 8使編寫它們變得非常簡單!

使用Map<Long,MyObject> objectsByIDs; 通過id存儲數據對象: objectsByIDs.put(id,myObjectInstance); 然后你可以通過這種方式從地圖中檢索它們MyObject o = objectsByIDs.get(id); 並從以下兩個中刪除它: objectsByIDs.remove(o); lst.remove(o) objectsByIDs.remove(o); lst.remove(o)

這確實很奇怪。 TreeSet.remove()的文檔明確聲明該方法調用equals()以便在Set中查找參數。 但是,刪除的堆棧跟蹤看起來像這樣

Thread [main] (Suspended (breakpoint at line 18 in MyObj))  
    MyObj.compareTo(MyObj) line: 18 
    MyObj.compareTo(Object) line: 1 
    TreeMap<K,V>.getEntry(Object) line: not available   
    TreeMap<K,V>.remove(Object) line: not available 
    TreeSet<E>.remove(Object) line: not available   
    MyObj.main(String[]) line: 45   

即使使用Comparator也行不通。 在我看來,像Sun / Oracle / openJDK的一些聰明的開發人員認為做compareTo()== 0和equals()相同。 它不是。

你唯一的選擇是使用外部數據結構來檢查建議的相等性,或者自己做循環,找到你想要的項目,然后將其刪除。

編輯:現在我明白了。 他們使用二進制搜索搜索項目,這就是他們做compareTo()的原因。

我認為您遇到的問題是您正在實現Comparable ,但您的實現似乎與equals不一致 - 並且您沒有實現任何相等的方法。 那是:

當且僅當e1.compareTo(e2)== 0與c1的每個e1和e2的e1.equals(e2)具有相同的布爾值時,C類的自然排序被認為與equals一致。

在您的情況下,當您構建這三個對象時:

MyObj o1 = new MyObj(99,1);
MyObj o2 = new MyObj(11,9);
MyObj o3 = new MyObj(1234, 1);

你會看到o1.compareTo(o3)== -1,而o1.equals(o3)== false。

但你似乎想要o1.equals(o3)== true。

此外,如果對象已存在於集合中,則認識到TreeSet.add()返回false。 此檢查基於equals()方法。

要解決此問題,請覆蓋Object.equals()和Object.hashCode(),以便它們考慮MyObj.id字段,並在compareTo()方法不相等時繼續使用sort_1字段。

package javaapplication1;

import java.util.TreeSet;

public class MyObj implements Comparable<MyObj> {

    public long sort_1;
    public long id;

    public MyObj(long sort, long id) {
        this.sort_1 = sort;
        this.id = id;
    }

    @Override
    public int compareTo(MyObj other) {
        return (this.equals(other))? 0 : Long.compare(sort_1, other.sort_1);
    }

    @Override
    public boolean equals(Object obj) {
        MyObj other = (MyObj) obj;
        return this.id == other.id && this.sort_1 == other.sort_1;
    }

    @Override
    public int hashCode() {
        return (int) id;
    }


    public String toString() {
        return id + ":" + sort_1;
    }

    public static void main(String[] args) {
        TreeSet<MyObj> lst = new TreeSet<MyObj>();

        MyObj o1 = new MyObj(99L, 1L);
        MyObj o2 = new MyObj(11L, 9L);
        MyObj o3 = new MyObj(1234L, 1L);       
        MyObj o4 = new MyObj(1234L, 1L);   

        System.out.println( "Adding o1: " + lst.add(o1));
        System.out.println( "Adding o2: " + lst.add(o2));
        System.out.println( "Adding o3: " + lst.add(o3));
        System.out.println( "Adding o4: " + lst.add(o4));        

        System.out.println(lst);

        System.out.println("o1.compareTo(o3) : " + o1.compareTo(o3));
        System.out.println("o1.equals(o3) : " + o1.equals(o3));

        //remove myObje with id 1
        boolean remove = lst.remove(o3);

        System.out.println(lst);
    }

}

輸出:

Adding o1: true
Adding o2: true
Adding o3: true
Adding o4: false
[9:11, 1:99, 1:1234]
o1.compareTo(o3) : -1
o1.equals(o3) : false
[9:11, 1:99]

TreeSet有一個不同的remove和排序邏輯幾乎肯定是不可能的,即使有可能它會破壞,如果你看它有趣。 不要那樣做。

試圖搞砸比較器,所以它做了一些神奇的事情是一個可怕的想法。 接受TreeSet將關注的只有一個比較概念。 你想用另一個比較概念做的事情不應該使用TreeSet方法來做到這一點。

你可以做的是有一個特殊的removeId(int)方法,它可以做類似的事情

void removeId(int id) {
  Iterator<MyObj> itr = set.iterator();
  while (itr.hasNext()) {
    if (itr.next().id == id) {
      itr.remove();
      break;
    }
  }
}

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM