【问题标题】:java TreeSet: comparing and equalityjava TreeSet:比较和相等
【发布时间】:2015-06-10 13:19:05
【问题描述】:

我想要使用属性“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]

我需要对列表进行排序,因为我在列表中添加了很多内容。我不想明确使用任何“排序”方法。我有什么选择?

编辑:

我的要求是:具有“id”的对象是唯一的,但可以有具有重复“排序”值的对象。

【问题讨论】:

  • 您可以使用地图而不是集合。该地图使用id作为键和MyObj(sort,id)作为值。
  • 您是否尝试为将查询 id 字段的 MyObj 指定 equals()?

标签: java treeset sortedset


【解决方案1】:

我昨天也偶然发现了这一点。这似乎是 TreeMap 实现的产物(TreeSet 使用它来存储其条目)。

TreeMap 使用二叉搜索树来存储键/值对,但它只使用给定的比较器(或者如果键类实现 Comparable,则使用比较函数)来检查相等性,正如您在这段代码摘录中看到的那样:

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;
}

我几乎称这是一个(不是真正可修复的)错误,因为 JavaDoc of the Comparable interface 明确表示使用 compareTo 函数返回 0 不一定意味着“相等”:

强烈建议但不严格要求 (x.compareTo(y)==0) == (x.equals(y))。

您将无法按照您希望的方式在 TreeSet 中存储内容。我建议使用普通的HashMapLinkedHashMap,然后在需要使用Collections.sort 对其进行排序时对输​​出进行排序。

除此之外,我总是觉得实现 Comparable 接口很奇怪。大多数事物并没有真正显而易见的“自然”顺序。有时这会导致奇怪的错误(比如这个!),所以我通常只在需要时使用自定义比较器进行排序。 Java 8 也让编写这些变得非常容易!

【讨论】:

    【解决方案2】:

    我认为您遇到的问题是您正在实现 Comparable,但您的实现似乎与 equals 不一致 - 而且您还没有实现任何相等方法。那就是:

    当且仅当 e1.compareTo(e2) == 0 对于类 C 的每个 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]
    

    【讨论】:

    • 这是一个很好的答案,但不幸的是,您的解决方案不允许 sort 值重复。我想我会接受这一点,因为我没有在问题中提到这一点。在您的解决方案中,似乎不允许“id”和“sort”值重复。我的要求是:'id' 是唯一的,但可以有具有重复 'sort' 值的对象。
    • @knocker_d 就TreeSet 而言,无论您要检查的排序顺序必须都是没有重复的。根据比较器,任何相同的东西总是被视为重复。
    • 我的解决方案可以防止重复,这也是 Set 和 Map 接口的主要用途之一。这是通过防止添加已经在 Set 中的对象来完成的。如果两个字段的组合都是键,那么您可以在 equals 方法中同时使用 ID 和 sort_1,方法是将 return 更改为:return this.id == other.id && this.sort_1 == other.sort_1;我将编辑上面的源代码来做到这一点。
    • 这个答案错误地提到“另外,如果对象已经存在于集合中,请认识到 TreeSet.add() 返回 false。此检查基于 equals() 方法。”请注意,TreeSet 仅依靠比较器实现来查找元素的存在。
    【解决方案3】:

    使用 Map&lt;Long,MyObject&gt; objectsByIDs; 按 id 存储您的数据对象:objectsByIDs.put(id,myObjectInstance);。然后您可以通过这种方式从地图中检索它们MyObject o = objectsByIDs.get(id); 并将其从两者中删除:objectsByIDs.remove(o); lst.remove(o)

    【讨论】:

      【解决方案4】:

      这确实很奇怪。 The documentation of TreeSet.remove() 明确指出该方法调用 equals() 以便在 Set 中找到参数。 但是,remove 的堆栈跟踪看起来像这样

      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() 的原因。

      【讨论】:

        【解决方案5】:

        remove 使用不同的逻辑并在TreeSet 中进行排序几乎肯定是不可能的,即使有可能,如果你看起来很有趣,它也会崩溃。 不要那样做。

        试图弄乱比较器,让它做一些神奇的事情是一个糟糕的主意。接受TreeSet 将关心的比较概念只有一个。任何你想用另一种比较概念做的事情都不应该使用TreeSet 方法来做到这一点。

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

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

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2023-03-25
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2019-01-16
          相关资源
          最近更新 更多