java - 如何保证 Set.removeAll 对于不同类型集合的行为?

标签 java collections set removeall

我在 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 只是提供一个简单的示例。事实上,我正在使用一个来自库的最终类,所以我无法轻易改进它。 但即使考虑到正确实现 hashCodeequals 的有效类,我的问题仍然存在。现在考虑一下:

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/

相关文章:

Java 集合 - 前 n 个和后 n 个元素

set - 对字符串进行分区的所有方法

java - 尝试将对象添加到列表并将其保存在 Spring Boot 上的 MySQL 中时出现 UnsupportedOperationException

java - 使用 Java 进行 Android 图像编辑/变形?

java - 使用注释 @SuppressWarnings 忽略 Checkstyle 警告

java - 如何在 IE 选项卡而不是新窗口中打开 URL - Java

java - 如何在 Spring 中将 AutopopulatedList<ChildClass> 引用到 List<AbstractBaseClass> ?

java - UnsatisfiedDependencyException 在 SpringBoot 中使用 @DataJpaTest

Java Set - 如何根据名称列表进行排序

python - 迭代相同的 freezeset 是否保证始终以相同的顺序生成项目? (Python 3)