【问题标题】:HashSet adding equivalent mutable objectsHashSet 添加等效的可变对象
【发布时间】:2014-07-30 16:02:29
【问题描述】:

我知道我应该使 Public Class 不可变,但我仍然在将 Person 对象添加到类型为 HashSet 的 Set 集合之前更改了它的名称,并且我的 Person 类还实现了 hashCode() 和等于() 方法有助于检查对象是否与前一个对象相同,并在 hashCode() 和 equals() 方法的帮助下防止添加到列表中,但输出仍然是: 鲍勃 查理 鲍勃

而不是: 鲍勃 查理

如果我替换主类的代码段:

Set<Person> set = new HashSet<Person>();
        Person a= new Person("alice",45);
        Person b=new Person("bob",41);
        Person c= new Person("charlie",48);
        set.add(a);
        a.name="bob";
        set.add(b);
        set.add(c);

通过下面的代码(即我直接声明了一个等效对象,而不像原来的有问题的代码那样稍后更改名称):

Set<Person> set = new HashSet<Person>();
        Person a= new Person("bob",45);
        Person b=new Person("bob",41);
        Person c= new Person("charlie",48);
        set.add(a);
        set.add(b);
        set.add(c);

那么对象b就不加了。

还有一点需要注意的是,我在添加对象 b 之前更改了对象的名称(在原始有问题的代码中)a 但仍然添加 b 为什么????

下面详细给出了包含主类和公共类的原始问题代码::

//Main.java class of generics_practice_test package;
package generics_practice_test;
import java.util.Set;
import java.util.HashSet;
import java.util.Iterator;
public class Main {


    public static void main (String args[])
    {
        Set<Person> set = new HashSet<Person>();
        Person a= new Person("alice",45);
        Person b=new Person("bob",41);
        Person c= new Person("charlie",48);
        set.add(a);
        a.name="bob";
        set.add(b);
        set.add(c);


        for(Iterator<Person> iterator=set.iterator();iterator.hasNext();){

            System.out.println(iterator.next());

        }



    }


}

Person 类的代码如下: //generics_practice_test包的Person类如下

package generics_practice_test;

public class Person implements Comparable<Person> {

    public String name;

    public int age;

    public Person(String name,int age)
    {
        this.name=name;
        this.age=age;
    }

    public String toString()
    {
        return this.name;
    }

    public int compareTo(Person o)
    {
        int myLength=name.length();
        int oLength=o.name.length();
        if(myLength == oLength) return 0;
        if(oLength > myLength) return -1;
        return 1;

    }

    @Override
    public int hashCode() {
        final int prime = 31;
        int result = 1;
        result = prime * result + ((name == null) ? 0 : name.hashCode());
        return result;
    }

    @Override
    public boolean equals(Object obj) {
        if (this == obj)
            return true;
        if (obj == null)
            return false;
        if (getClass() != obj.getClass())
            return false;
        Person other = (Person) obj;
        if (name == null) {
            if (other.name != null)
                return false;
        } else if (!name.equals(other.name))
            return false;
        return true;
    }

}

【问题讨论】:

  • b 仍添加在a.name="bob" 之后,因为a 与其旧哈希一起存储。更改a.name 会更改a.hashCode() 返回值,但包含HashSet 无法知道这一点——因此a 仍使用其旧哈希(与b.hashCode() 不同)保存在存储中跨度>
  • 谢谢 blgt 先生,它帮助了。

标签: java hashset


【解决方案1】:

HashSet 将根据hashCode 方法的结果存储元素。这是实现中使用的伪算法:

  • 检索对象hashCode。将其存储在int possibleIndex
  • 根据possibleIndex获取可以存储对象的索引。将其存储在index
  • 检索存储在index 中的List。遍历此列表并使用equals 方法查找对象是否不在此列表中。这样做是为了处理冲突或具有相同hashCode 的对象。

请注意,对于您的示例,Person#hashCode 实现仅依赖于 name 元素。

让我们回顾一下你的第一段代码:

//create the new HashSet
Set<Person> set = new HashSet<Person>();
//create your elements to be inserted
Person a= new Person("alice",45);
Person b= new Person("bob",41);
Person c= new Person("charlie",48);
//try to add "a". It will calculate hashCode from name field,
//which value is "alice"
set.add(a);
//you change the name here, but it won't affect the previous
//operation because the index for "a" was calculated using "alice",
//not "bob" and IT WONT BE RECALCULATED!
a.name="bob";
//try to add "b". It will calculate hashCode from name field,
//which value is "bob"
//as noted before, there's no index based on "bob"'s hashCode,
//so it will be added with no problems
set.add(b);
//try to add "c". It will calculate hashCode from name field,
//which value is "charlie"
set.add(c);

所以,所有元素都插入没有问题,因为hashCode 没有冲突(这就是为什么前面的解释不包括对equals 方法的调用)。

现在,让我们回顾一下您的第二段代码:

//create the new HashSet
Set<Person> set = new HashSet<Person>();
//create your elements to be inserted
Person a= new Person("bob",45);
Person b=new Person("bob",41);
Person c= new Person("charlie",48);
//try to add "a". It will calculate hashCode from name field,
//which value is "bob"
set.add(a);
//try to add "b". It will calculate hashCode from name field,
//which value is "bob"
//since the index calculated from the hashCode of "bob" is
//already inserted, it will check if the element already exists
//Looking at Person#equals, which is based on name field only
//there is an element where the name field has a value of "bob"
//"b" won't be inserted
set.add(b);
//try to add "c". It will calculate hashCode from name field,
//which value is "charlie"
set.add(c);

【讨论】:

  • 谢谢 Luiggi Mendoza 先生,答案很棒。
【解决方案2】:

HashSet 内部使用HashMap,HashMap 是hashtable 数据结构的实现。

Hashtables 使用了将数据存储在桶中的概念。

把它想象成一张地图,其中键是hashCode,值是元素列表。

现在这里发生的是,在存储时,它会获取hashCode() 并将当前元素存储在任何可能的位置。

在你的情况下它是name hashCode。现在以您的 bob 的哈希值为 32 为例,因此会有一个条目以 32 作为键,而列表将包含该元素。

您更改名称并不会真正改变元素的位置。

在插入 HashSet 时,请按照以下步骤操作。

  • 获取对象的hashCode值
  • 搜索具有该值的存储桶。
  • 如果找到存储桶,则遍历列表并使用当前对象对所有存储桶调用 equals。
  • 如果找到对象则不插入,否则插入。

【讨论】:

    【解决方案3】:

    尝试将您的 equals 方法更改为此

     @Override
        public boolean equals(Object obj) {
    
        if (obj == null) //first you check if object is null
            return false;
        if (getClass() != obj.getClass()) // then you check if object is of the same class
            return false;
        Person other = (Person) obj;
        if (name == null) {
            if (other.name != null)
                return false;
        } else if (!name.equals(other.name))
            return false;
        return true;
    }
    

    语句if (this == obj) 按内存中的地址检查对象。每个对象在内存中的地址都会不同!如果你在对象上使用了new

    【讨论】:

    • 感谢您的时间和精力,非常感谢。
    • 问题不在于equals方法实现。此外,使用this == obj 是体面的equals 对象实现的一部分。如果您想更改行为,请使用 age 字段添加新验证。
    猜你喜欢
    • 2016-05-23
    • 2018-11-16
    • 1970-01-01
    • 1970-01-01
    • 2013-03-21
    • 2016-01-21
    • 2015-01-18
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多