HashSet中消失的元素和多出来的元素

原创 2015年11月20日 23:16:57

首先给出一个辅助类:

package disappearElementInHashset;

public class Person {
	private String name;

	public Person(String name) {
		super();
		this.name = name;
	}

	public String getName() {
		return name;
	}

	public void setName(String name) {
		this.name = name;
	}

	@Override
	public String toString() {
		return "Person [name=" + name + "]";
	}

}

需要注意的是,这个类没有重写Object类的hashCode方法和equals方法。接下来给出一个测试类引出我们的主题:

package disappearElementInHashset;

import java.util.HashSet;
import java.util.Iterator;

public class Test {

	public static void main(String[] args) {
		HashSet<Person> personSet = new HashSet<>();
		Person me = new Person("liyuncong");
		personSet.add(me);
		me.setName("wang");
		personSet.add(me);
		
		Iterator<Person> iterator = personSet.iterator();
		while(iterator.hasNext()) {
			System.out.println(iterator.next());
		}
                System.out.println(personSet.size());
 }

}

测试类的打印结果:

Person [name=wang]

1


我们往HashSet里放了两个元素,可是为什么只有一个元素呢,而且只有后面放进去的那个元素?(!!!提醒一下,后面这段解释是不对的)其实这是很好理解,因为集合中不能存在相等的元素,而me在修改前后是相等的,之所以me在修改前后是相等的,是因为Person并没有重写Object类的equals方法,Person实例的相等性比较,只是比较地址而已,而me指向的对象的值虽然被修改了,但是me始终都指向同一块空间。因此,第二个me并没有放进去。从HasHSet中打印出来的值成了me指向对象修改的值,是因为HashSet中保存的引用和me指向同一块地方。

这个解释听上去很有道理,但其实是不完整的。接下来,我们往Person里加入自定义的hashCode方法和equals方法。如下:

package disappearElementInHashset;

public class Person {
	private String name;

	public Person(String name) {
		super();
		this.name = name;
	}

	public String getName() {
		return name;
	}

	public void setName(String name) {
		this.name = name;
	}

	@Override
	public String toString() {
		return "Person [name=" + name + "]";
	}

	@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;
	}
	
}

这时,我们再次运行上面的测试类,结果如下:

Person [name=wang]
Person [name=wang]
2

HashSet里面放了两个一样的元素,惊到我了。我们都敢确定,打印出来的两个对象是相等的,(它们不仅值相等,而且地址页相等,因为它们就是同一个对象,自始至终都只有一个对象,流浪在外的只是指向这个对象的多个指针),可是怎么都放进去了呢?这时,我觉得有必要看看HashSet的add方法的源码了。因为HashSet是基于HashMap实现的,于是,我们只需要看看HashMap的源码就行了,源码如下:

	public V put(K key, V value) {
		// 如果table之前为空,就创建一个更大的table
        if (table == EMPTY_TABLE) {
            inflateTable(threshold);
        }
        // 如果key为null,就用对应的put方法
        if (key == null)
            return putForNullKey(value);
        // 计算key的哈希值
        int hash = hash(key);
        // 根据哈希值和table长度计算该键值对应该存储在那个桶中
        int i = indexFor(hash, table.length);
        // 如果这个桶中拥有键和正在添加的键值对的键相同的键值对,就只是更改这个已有的键值对得值
        for (Entry<K,V> e = table[i]; e != null; e = e.next) {
            Object k;
            if (e.hash == hash && ((k = e.key) == key || key.equals(k))) {
                V oldValue = e.value;
                e.value = value;
                e.recordAccess(this);
                return oldValue;
            }
        }

        modCount++;
        // 如果这个桶中没有和正在添加的键值对的键相同的键值对,就把这个键值对添加到这个桶中
        addEntry(hash, key, value, i);
        return null;
    }

这些明白了。HashSet是先通过对象的哈希值把对象找到相应的桶,然后再桶中寻找有没有相等对象。我们的程序中虽然自始至终都只有一个对象,时时刻刻都与自身相等,但是修改前后的哈希值却不同,第一次把指向对象的引用放在了一个桶中,而第二次却是在另一个桶中寻找是否已经存在相等的元素,就这样,程序中的结果产生了。

在最初的程序中,之所以只放进去了一个元素,是因为Object类的hashCode方法通过地址计算哈希值,地址没变,哈希值也就没变,于是第二个引用是试图放在和第一个引用同一个桶中,自然失败了。

程序中这样的问题其实是非常隐蔽的,所以,编码时,要关注各种引用之间千丝万缕的联系,还有关注自己类中equals方法和hashCode方法的定义。

版权声明:本文为博主原创文章,未经博主允许不得转载。

相关文章推荐

HashSet中是如何判断元素是否重复的

HashSet不能添加重复的元素,当调用add(Object)方法时候, 首先会调用Object的hashCode方法判hashCode是否已经存在,如不存在则直接插入元素; 如果已存在则调用...

HashMap和HashSet(深入HashMap源码分析HashMap元素的存储)

深入HashMap源码分析HashMap元素的存储
  • canot
  • canot
  • 2016年04月24日 16:55
  • 498

关于HashSet添加元素时的equals()方法和hashCode()方法

HashSet集合不能存储重复的元素,那么元素之间是否重复,HashSet是根据什么机制去判断的呢?HashSet在添加一个元素时(比如此时添加的是”a”这个元素),都会将该元素与set中所遍历到的每...

关于 Arraylist和HashSet中元素比较的问题所引发的思考

问题源自我在论坛回复网友的一个帖子,名字就是【关于 Arraylist和HashSet中元素比较的问题】,问题如下: import java.util.ArrayList; import java....

HashSet如何保证元素唯一性?

Collection中的Set分为HashSet和TreeSet Set中的元素是无序的,即存入和取出的顺序不一定一致,元素不可以重复。HashSet的底层数据结构是哈希表,元素存入的顺序是按照哈希...
  • G626316
  • G626316
  • 2017年02月24日 14:32
  • 178

【Java】HashMap、HashSet、TreeMap、TreeSet判断元素相同(代码整理)

1.1     HashMap        先来看一下HashMap里面是怎么存放元素的。Map里面存放的每一个元素都是key-value这样的键值对,而且都是通过put方法进行添加的,而且相同的...
  • hj7jay
  • hj7jay
  • 2017年01月03日 10:12
  • 408

IE绝对定位元素始终被遮挡或者消失的解决方法

最近做一个下拉菜单,当鼠标悬停在主菜单上,显示下拉菜单。ie8和FF都很正常,但是 ie6下拉菜单内容始终被下边的内容遮挡。试了很多种解决办法。发现原来是IE的bug。 解决方法如下:1.当绝对定位层...
  • xhssky
  • xhssky
  • 2011年04月07日 12:14
  • 801

JAVA基础之——HashSet中是如何判断元素是否重复的

原文:http://blog.csdn.net/ning109314/article/details/17354839 HashSet不能添加重复的元素,当调用add(Object)方法时候...

HashSet中是如何判断元素是否重复的

HashSet不能添加重复的元素,当调用add(Object)方法时候, 首先会调用Object的hashCode方法判hashCode是否已经存在,如不存在则直接插入元素; 如果已存在则调用Ob...

对Java如何判断HashSet和HashMap中相同元素的研究

转自:http://f543711700.iteye.com/blog/800929 对Java如何判断HashSet和HashMap中相同元素的研究 Java数据结构JDK  ...
内容举报
返回顶部
收藏助手
不良信息举报
您举报文章:HashSet中消失的元素和多出来的元素
举报原因:
原因补充:

(最多只允许输入30个字)