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' 值的对象。
使用 Map<Long,MyObject> objectsByIDs;
按 ID 存储您的数据对象:objectsByIDs.put(id,myObjectInstance);
。然后您可以通过这种方式从地图中检索它们MyObject o = objectsByIDs.get(id);
并将其从两者中删除:objectsByIDs.remove(o); lst.remove(o)
.
这确实很奇怪。 The documentation of 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() 的原因。
我昨天也偶然发现了这个。这似乎是 TreeMap 实现的产物(TreeSet 用来存储其条目)。
TreeMap 使用二叉搜索树来存储 key/value 对,但它只使用给定的 Comparator(或者如果键 class 实现 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 并不一定意味着 "equalness":
It is strongly recommended, but not strictly required that (x.compareTo(y)==0) == (x.equals(y)).
您将无法按照您希望的方式在 TreeSet 中存储内容。我建议使用正常的 HashMap or a LinkedHashMap and then just sorting the output when you need to sort it with Collections.sort.
除此之外,我总是觉得实现 Comparable 接口很奇怪。大多数事情实际上并没有立即显而易见的 "natural" 顺序。有时这会导致奇怪的错误(比如这个错误!),所以我通常只在需要时才使用自定义比较器进行排序。 Java 8 也使编写这些内容变得非常容易!
我认为您遇到的问题是您正在实施 Comparable,但您的实施似乎与 equals 不一致 - 而且您还没有实施任何平等方法。即:
The natural ordering for a class C is said to be consistent with equals if and only if e1.compareTo(e2) == 0 has the same boolean value as e1.equals(e2) for every e1 and e2 of class C
在您的情况下,当您构建这三个对象时:
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() returns false。此检查基于 equals() 方法。
为了解决这个问题,覆盖 Object.equals() 和 Object.hashCode() 以便它们考虑 MyObj.id 字段,并继续使用 sort_1 字段当它们不相等时在 compareTo() 方法中。
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]
对 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;
}
}
}
我希望对象列表按 属性 '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' 值的对象。
使用 Map<Long,MyObject> objectsByIDs;
按 ID 存储您的数据对象:objectsByIDs.put(id,myObjectInstance);
。然后您可以通过这种方式从地图中检索它们MyObject o = objectsByIDs.get(id);
并将其从两者中删除:objectsByIDs.remove(o); lst.remove(o)
.
这确实很奇怪。 The documentation of 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() 的原因。
我昨天也偶然发现了这个。这似乎是 TreeMap 实现的产物(TreeSet 用来存储其条目)。
TreeMap 使用二叉搜索树来存储 key/value 对,但它只使用给定的 Comparator(或者如果键 class 实现 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 并不一定意味着 "equalness":
It is strongly recommended, but not strictly required that (x.compareTo(y)==0) == (x.equals(y)).
您将无法按照您希望的方式在 TreeSet 中存储内容。我建议使用正常的 HashMap or a LinkedHashMap and then just sorting the output when you need to sort it with Collections.sort.
除此之外,我总是觉得实现 Comparable 接口很奇怪。大多数事情实际上并没有立即显而易见的 "natural" 顺序。有时这会导致奇怪的错误(比如这个错误!),所以我通常只在需要时才使用自定义比较器进行排序。 Java 8 也使编写这些内容变得非常容易!
我认为您遇到的问题是您正在实施 Comparable,但您的实施似乎与 equals 不一致 - 而且您还没有实施任何平等方法。即:
The natural ordering for a class C is said to be consistent with equals if and only if e1.compareTo(e2) == 0 has the same boolean value as e1.equals(e2) for every e1 and e2 of class C
在您的情况下,当您构建这三个对象时:
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() returns false。此检查基于 equals() 方法。
为了解决这个问题,覆盖 Object.equals() 和 Object.hashCode() 以便它们考虑 MyObj.id 字段,并继续使用 sort_1 字段当它们不相等时在 compareTo() 方法中。
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]
对 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;
}
}
}