我的 HashSet 在修改后存储了重复的对象

问题描述 投票:0回答:1

我是一名编程导师。昨天,我试图通过一些测试用例向我的学生解释 HashSet。当然,HashSet 允许修改包含的对象,但它可能会导致不一致问题,我向他们展示了一些正确执行此操作的方法。然后,我遇到了一种情况,我的HashSet添加了一个修改后已经在其中的对象。谁能给我解释一下为什么会这样吗?

这是我的代码:

boolean cambiar = false;
Persona personaCambiada = null;
Iterator<Persona> iteratorHS = hs.iterator();
 
while (iteratorHS.hasNext()) {
    Persona persona = iteratorHS.next();
  
    if (cambiar == false) {
        personaCambiada = persona;
        iteratorHS.remove();
        personaCambiada.setDNI(p2.getDNI());
        personaCambiada.setNombre(p2.getNombre());
        cambiar = true;
    }
}

System.out.println(p2.equals(personaCambiada)); // prints true
System.out.println(personaCambiada.equals(p2)); // prints true

hs.add(personaCambiada); // store the object but it shouldn't happen

人物角色:

@Override
public boolean equals(Object o) {
    if (this == o) return true;
    if (o == null || getClass() != o.getClass()) return false;
    Persona persona = (Persona) o;
    return Objects.equals(DNI, persona.DNI) && Objects.equals(nombre, persona.nombre);
}

@Override
public int hashCode() {
    return Objects.hash(DNI, nombre, edad);
}

我试图证明HashSet在修改后不能添加重复的对象。

java iterator hashset
1个回答
0
投票

您的 hashCode 方法意味着如果

DNI
nombre
edad
不同,哈希值将会更改。

您的 equals 方法意味着,如果任意 2 个对象的

DNI
nombre
值相等,则它们相等。

您有 2 个对象:它们具有相同的 DNI 和名称,但 edad 不同。因此,

a.equals(b)
,还有
a.hashCode() != b.hashCode()

根据文档,您违反了合同。而且合同很明确:如果你违反了它,HashSet 就可以做疯狂的事。

遵守合同。合同要求:

  1. 对于所有
    a
    b
    a.equals(b) == b.equals(a)
  2. 对于所有人
    a
    a.equals(a)
  3. 对于所有
    a
    b
    c
    (a.equals(b) && b.equals(c)) == a.equals(c)
    。这比您想象的要棘手得多,这意味着不可能对任何东西进行子类化并添加状态,同时仍然允许子类的对象等于父类的对象。换句话说,制作一个在列表中的任何内容之上添加颜色属性的
    ColoredArrayList
    并且让蓝色空列表不等于红色空列表是不可能的。这个让很多人都绊倒了。
  4. 对于所有
    a
    b
    if (a.equals(b)) a.hashCode() == b.hashCode()
    - 请注意,相反的情况并不成立(任何具有相等 hashCode 值的 2 个对象不必相等。但是任何 2 个相等的对象 必须 具有相等的值哈希码)。 这就是你侵犯的
  5. equals
    hashCode
    不应该扔任何东西。

如果您遇到问题,请使用 Project Lombok 等工具或 IDE 的“生成等于和哈希码”选项。


关于此代码的注释

这很糟糕;你说你是一名导师,所以这更令人担忧:

  1. dni
    ,不是
    DNI
    。 Java 约定要求所有首字母缩略词无论如何都以驼峰形式书写。是
    class DvdPlayer
    ,不是
    class DVDPlayer
    。 JDK 本身遵循这一点...除了那些太旧的类,它们早于该规则,例如
    java.net.URL
    类,它的名称错误,应该是
    java.net.Url
    ,但是,更改它不向后兼容。原因很简单:你无法分辨(是
    DV D Player
    还是
    DVD Player
    ?使用
    DvdPlayer
    时,它的
    Dvd Player
    - 很清楚“单词之间的分隔符”在哪里。此外,使用
    DNI
    时,是是常量(全部大写)还是变量名?它是
    iteratorHs
    ,而不是
    iteratorHS

  2. if (cambiar == false)
    应该写成
    if (!cambiar)

  3. 您的 while 循环具有误导性。因为它实际上并不循环。它运行一次。或者更确切地说,它循环遍历所有内容,但除了第一个元素之外绝对不执行任何操作。你真正想要的是:

var it = hs.iterator();
Persona personaCambiada = it.next();
it.remove();

重要的是,代码不会误导(误导性代码,例如,乍一看表明 X 正在发生,而实际上 X 并未发生的代码,是迄今为止最糟糕的情况。难以阅读的代码要好得多;至少不经意的浏览者会知道他们要么必须深入研究它的作用,要么接受他们不知道它是如何工作的,这比一个不经意的浏览者认为他们知道它的作用但错误的想法要好得多)。当错误时,此代码会通过关注

while
部分来随意误导。

© www.soinside.com 2019 - 2024. All rights reserved.