我在 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
文档的这一部分:
Note that the ordering maintained by a set (whether or not an explicit comparator is provided) must be consistent with equals if it is to correctly implement the Set interface. (See Comparable or Comparator for a precise definition of consistent withequals.) This is so because the Set interface is defined interms of the equals operation, but a TreeSet instance performs all element comparisons using its compareTo (or compare) method, so two elements that are deemed equal by this method are, from the standpoint of the set, equal. The behavior of a set is well-defined even if its ordering is inconsistent with equals; it just fails to obey the general contract of the Set interface.
如果我理解正确,我可以使用 TreeSet
来达到我的目的,但不能指望它的行为像我想要的那样。
感谢大家的帮助。
最佳答案
AtomicReference a = new AtomicReference<>("A");
AtomicReference a2 = new AtomicReference<>("A");
System.out.println(a==a2);
你的答案就在其中。
如果您使用自定义类而不是Object
,并且覆盖 equals 方法,它将按预期工作。
让它发挥作用
class AtomicString{
private AtomicReference<String> s;
public AtomicString(String s) {
this.s = new AtomicReference<>(s);
}
@Override public boolean equals(Object o) {
if (this == o)
return true;
if (o == null || getClass() != o.getClass())
return false;
AtomicString that = (AtomicString) o;
return this.s.get().equals(that.getS().get());
}
public AtomicReference<String> getS() {
return s;
}
@Override public int hashCode() {
return Objects.hash(s.get());
}
关于java - 如何保证 Set.removeAll 对于不同类型集合的行为?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/53230222/