【发布时间】:2019-04-13 06:55:40
【问题描述】:
我对 HashSet 和 TreeSet 操作有疑问。 这是一个解释我的问题的简单 JUnit 4 测试:
import java.util.HashSet;
import java.util.TreeSet;
import java.util.concurrent.atomic.AtomicReference;
import org.junit.Assert;
import org.junit.Test;
public class TreeSetTest<T> {
@Test
public void test() {
final HashSet<Object> set1 = new HashSet<>();
final TreeSet<Object> set2 = new TreeSet<>((a, b) -> a.toString().compareTo(b.toString()));
Assert.assertEquals(0, set1.size()); // OK
Assert.assertEquals(0, set2.size()); // OK
set1.add(new AtomicReference<>("A"));
set1.add(new AtomicReference<>("B"));
set1.add(new AtomicReference<>("C"));
Assert.assertEquals(3, set1.size()); // OK
Assert.assertEquals(0, set2.size()); // OK
set1.removeAll(set2);
Assert.assertEquals(3, set1.size()); // OK
Assert.assertEquals(0, set2.size()); // OK
set2.add(new AtomicReference<>("A"));
set1.removeAll(set2);
Assert.assertEquals(3, set1.size()); // OK Nothing has been removed
Assert.assertEquals(1, set2.size());
set2.add(new AtomicReference<>("B"));
set1.removeAll(set2);
Assert.assertEquals(3, set1.size()); // OK Nothing has been removed
Assert.assertEquals(2, set2.size());
set2.add(new AtomicReference<>("C"));
set1.removeAll(set2);
Assert.assertEquals(3, set1.size()); // Error Everything has been removed and size is now 0
Assert.assertEquals(3, set2.size());
}
}
当从set1 中删除set2 的所有元素时,我希望使用set1 的相等比较器,只要set2 的大小小于set1 的大小就可以了但如果set2 的大小大于或等于set1 的大小,则从set2 进行比较。
这对我来说非常糟糕,因为它使程序变得不可预测。
我认为它可以被视为 java 实现中的一个错误,但我担心的是: 如何在不重写所有内容的情况下保证预期的行为?
在@FedericoPeraltaSchaffner 评论后编辑 1:
AtomicReference 只是为了提供一个简单的例子。事实上,我正在使用一个库中的最终类,所以我不能轻易改进它。
但即使考虑正确实现hashCode 和equals 的有效类,我的问题仍然存在。现在考虑一下:
package fr.ncenerar.so;
import java.util.HashSet;
import java.util.TreeSet;
import org.junit.Assert;
import org.junit.Test;
public class TreeSetTest<T> {
public static class MyObj {
private final int value1;
private final int value2;
public MyObj(final int v1, final int v2) {
super();
this.value1 = v1;
this.value2 = v2;
}
public int getValue1() {
return this.value1;
}
public int getValue2() {
return this.value2;
}
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + this.value1;
result = prime * result + this.value2;
return result;
}
@Override
public boolean equals(final Object obj) {
if (this == obj) {
return true;
}
if (obj == null) {
return false;
}
if (this.getClass() != obj.getClass()) {
return false;
}
final MyObj other = (MyObj) obj;
if (this.value1 != other.value1) {
return false;
}
if (this.value2 != other.value2) {
return false;
}
return true;
}
}
@Test
public void test() {
final HashSet<MyObj> set1 = new HashSet<>();
final TreeSet<MyObj> set2 = new TreeSet<>((a, b) -> a.getValue1() - b.getValue1());
Assert.assertEquals(0, set1.size()); // OK
Assert.assertEquals(0, set2.size()); // OK
set1.add(new MyObj(0, 0));
set1.add(new MyObj(1, 1));
set1.add(new MyObj(2, 2));
Assert.assertEquals(3, set1.size()); // OK
Assert.assertEquals(0, set2.size()); // OK
set1.removeAll(set2);
Assert.assertEquals(3, set1.size()); // OK
Assert.assertEquals(0, set2.size()); // OK
set2.add(new MyObj(0, 1));
set1.removeAll(set2);
Assert.assertEquals(3, set1.size()); // OK Nothing has been removed
Assert.assertEquals(1, set2.size());
set2.add(new MyObj(1, 2));
set1.removeAll(set2);
Assert.assertEquals(3, set1.size()); // OK Nothing has been removed
Assert.assertEquals(2, set2.size());
set2.add(new MyObj(2, 3));
set1.removeAll(set2);
Assert.assertEquals(3, set1.size()); // Error Everything has been removed
Assert.assertEquals(3, set2.size());
}
}
问题依然存在,MyObj 的实现是正确的。问题来自我从两个不同方面使用对象的事实。在一组中,我想根据它们的相等性保留每个对象的一个实例(如对象的equals 方法),在另一组中,我想要第一组的一个子集,其中对于每个value1,我只想保留第一个插入的元素。
使用TreeSet 似乎是有效的。
编辑 2:
我的错,我错过了TreeSet 文档的那一部分:
请注意,由集合维护的顺序(无论是否 提供了显式比较器)必须与equals一致,如果它 就是正确实现Set接口。 (见可比或 用于精确定义一致不等于的比较器。)这是 所以因为 Set 接口是根据等式定义的 操作,但 TreeSet 实例执行所有元素比较 使用它的 compareTo(或比较)方法,所以两个元素是 这种方法认为相等,从集合的角度来看,相等。 集合的行为是明确定义的,即使它的排序是 与equals不一致;它只是不遵守总合同 设置界面。
如果我理解正确,我可以使用 TreeSet 来达到我的目的,但不能指望它的行为像我想要的那样。
感谢大家的帮助。
【问题讨论】:
-
@FedericoPeraltaSchaffner 感谢您的评论。我更新了我的问题以添加一些信息。
-
你不能让传递给树集的比较器与equals一致吗? IE。仅当元素根据 equals 方法相等时才返回 0
-
@FedericoPeraltaSchaffner 不幸的是,没有。事实上,我使用的库为实际上相等的对象(从我的观点)创建了新实例(不同于
equals的观点)。
标签: java collections set removeall