如果修改了包含的元素,则Java HashSet包含重复项

shstlldc  于 2022-12-17  发布在  Java
关注(0)|答案(7)|浏览(155)

假设你有一个类,你创建了一个HashSet来存储这个类的示例,如果你试图添加相等的示例,集合中只会保留一个示例,这是可以的。
但是,如果HashSet中有两个不同的示例,并且您取了一个示例并使其成为另一个示例的精确副本(通过复制字段),则HashSet将包含两个重复的示例。
下面的代码演示了这一点:

public static void main(String[] args)
    {
         HashSet<GraphEdge> set = new HashSet<>();
        GraphEdge edge1 = new GraphEdge(1, "a");
        GraphEdge edge2 = new GraphEdge(2, "b");
        GraphEdge edge3 = new GraphEdge(3, "c");

        set.add(edge1);
        set.add(edge2);
        set.add(edge3);

        edge2.setId(1);
        edge2.setName("a");

        for(GraphEdge edge: set)
        {
            System.out.println(edge.toString());
        }

        if(edge2.equals(edge1))
        {
            System.out.println("Equals");
        }
        else
        {
            System.out.println("Not Equals");
        }
    }

    public class GraphEdge
    {
        private int id;
        private String name;

        //Constructor ...

        //Getters & Setters...

        public int hashCode()
        {
        int hash = 7;
        hash = 47 * hash + this.id;
        hash = 47 * hash + Objects.hashCode(this.name);
        return hash;    
        }

        public boolean equals(Object o)
        {
            if(o == this)
            {
                return true;
            }

            if(o instanceof GraphEdge)
            {
                GraphEdge anotherGraphEdge = (GraphEdge) o;
                if(anotherGraphEdge.getId() == this.id && anotherGraphEdge.getName().equals(this.name))
                {
                    return true;
                }
            }

                return false;
        }
    }

上面代码的输出:
是否有办法强制HashSet验证其内容,以便删除在上述场景中创建的可能重复的条目?
一个可能的解决方案是创建一个新的哈希集,并将内容从一个哈希集复制到另一个哈希集,这样新的哈希集就不会包含重复的内容,但我不喜欢这个解决方案。

fcipmucu

fcipmucu1#

您描述的情况无效。请参阅Javadoc:“当对象是集合中的一个元素时,如果对象值的更改方式会影响等于比较,则不会指定集合的行为。”

nc1teljy

nc1teljy2#

为了补充@EJP的答案,如果您改变HashSet中的对象使其成为副本(在equals/hashcode契约的意义上),实际上会发生什么,散列表数据结构将被破坏。

  • 根据变异的确切细节和哈希表的状态,一个或两个示例将变得不可见(例如contains和其他操作)。要么它在错误的哈希链上,要么是因为其他示例在哈希链上出现在它之前。很难预测哪个示例将是可见的......以及它是否仍然可见。
  • 如果迭代集合,两个示例仍然存在......这违反了Set约定。

当然,从应用程序的Angular 来看,这是非常破碎的。
您可以通过以下任一方法避免此问题:

  • 对集合元素使用不可变类型,
  • 在将对象放入集合和/或从集合中拉出对象时制作对象的副本,
  • 编写代码,使其“知道”在持续时间内不要更改对象...

从正确性和健壮性的Angular 来看,第一种选择显然是最好的。
顺便说一句,用一种通用的方式来“修复”这个问题是非常困难的。在Java中没有普遍的机制来知道......或者被通知......某个元素发生了变化。你可以在一个类接一个类的基础上实现这样的机制,但是它必须被显式地编码(而且代价不菲)。即使你有这样一个机制,你会怎么做?很明显,其中一个对象现在应该从集合中删除......但是是哪一个呢?

ma8fv8wu

ma8fv8wu3#

你是对的,我不认为有任何方法可以防止你所讨论的情况。所有使用哈希和等于的集合都受到这个问题的影响。集合没有通知对象自从被添加到集合以来已经更改。我认为你概述的解决方案是好的。
如果你如此关心这个问题,也许你需要重新考虑你的数据结构。你可以使用不可变的对象,比如说,有了不可变的对象,你就不会有这个问题。

xv8emn3q

xv8emn3q4#

HashSet不知道添加对象后其成员属性的更改。如果这对您来说是个问题,则可以考虑使GraphEdge不可变。例如:

GraphEdge edge4 = edge2.changeName("new_name");

GraphEdge不可变的情况下,更改值将导致返回新示例,而不是更改现有示例。

dy1byipe

dy1byipe5#

一种方法,可用于打印String对象的LinkedList的元素,而不打印任何重复的元素。该方法将LinkedList对象作为输入,然后创建一个新的HashSet对象。然后,该方法遍历输入LinkedList的元素,并将每个元素添加到HashSet中。由于HashSet不允许重复元素,因此这可确保仅将唯一元素添加到HashSet中。
然后,该方法遍历HashSet并将每个元素打印到控制台,元素之间用空格分隔。与printList方法不同,该方法不会在元素列表之前或之后添加任何换行符。它只是打印字符串“Non-duplicates are:“,然后是HashSet的元素。

public static void printSetList(LinkedList<String> list) {
    Set<String> hashSet = new HashSet<>();
    for (String v : list) {
        hashSet.add(v);
    }
    System.out.print("Non-duplicates are: ");
    for (String v : hashSet) {
        System.out.print(v + " ");
    }
}
v7pvogib

v7pvogib6#

hashCode用于使用参数对象生成hascode。您将其用作hascode计算的一部分。
尝试将hashCode的实现替换为以下代码:

public int hashCode()
{
    return Objects.hashCode(this.id, this.name);
}
qco9c6ql

qco9c6ql7#

你需要在你迭代列表的时候做唯一性检测。创建一个新的HashSet可能看起来不是正确的方法,但是为什么不试试这个呢...也许不使用HashSet开始...

public class TestIterator {
    public static void main(String[] args) {
        List<String> list = new ArrayList<String>();

        list.add("1");
        list.add("1");
        list.add("2");
        list.add("3");

        for (String s : new UniqueIterator<String>(list)) {
            System.out.println(s);
        }
    }
}

public class UniqueIterator<T> implements Iterable<T> {
    private Set<T> hashSet = new HashSet<T>();

    public UniqueIterator(Iterable<T> iterable) {
        for (T t : iterable) {
            hashSet.add(t);
        }
    }

    public Iterator<T> iterator() {
        return hashSet.iterator();
    }
}

相关问题