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中的元素比较详解

引例public class Student{ private String name; private int age; Student(String name,int ag...
  • disiwei1012
  • disiwei1012
  • 2016年06月03日 11:07
  • 1892

HashSet类是如何实现添加元素保证不重复的---哈希码的原理

弄清怎么个逻辑达到元素不重复的,源码先上 HashSet 类中的add()方法:public boolean add(E e) { return map.put(e, PRESENT)==n...
  • u010698072
  • u010698072
  • 2016年10月12日 23:37
  • 1911

将自定义对象存入到HashSet集合中并去除重复元素

package tan; import java.util.HashSet; import java.util.Iterator; class Student{ private int age; ...
  • u010834071
  • u010834071
  • 2014年07月19日 20:51
  • 1597

JS删除数组里的某个元素

删除数组指定的某个元素 首先可以给JS的数组对象定义一个函数,用于查找指定的元素在数组中的位置,即索引,代码为: Array.prototype.indexOf = function(val) { ...
  • chichengjunma
  • chichengjunma
  • 2017年01月01日 16:38
  • 6371

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

HashSet不能添加重复的元素,当调用add(Object)方法时候, 首先会调用Object的hashCode方法判hashCode是否已经存在,如不存在则直接插入元素; 如果已存在则调用Ob...
  • ning109314
  • ning109314
  • 2013年12月16日 16:13
  • 15877

javascript 三种方法实现获得和设置以及移除元素属性

获得和设置以及移除元素属性在操作dom的过程中会经常遇到吧,为了提高工作的效率本文整理了一些快捷操作方法和大家一起分享,感兴趣的朋友可以参考下哈 以下面的html为例  复制代码代码如...
  • ywb201314
  • ywb201314
  • 2016年11月28日 08:55
  • 395

关于NSMutableArray 删除元素的问题小结

在项目中遇到了一个问题,是将一个可变字典中的某些字符串进行删除操作,当时我的第一反应是采用forin 遍历,可能是觉得forin的遍历速度比较快吧,额,扯远了,回到遇到的问题中,当我用forin进行遍...
  • MingerW
  • MingerW
  • 2016年04月21日 14:26
  • 7504

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

原文:http://blog.csdn.net/ning109314/article/details/17354839 HashSet不能添加重复的元素,当调用add(Object)方法时候...
  • xuxiaoxie
  • xuxiaoxie
  • 2016年05月09日 19:46
  • 3594

HashSet如何判定两个元素相同

在介绍java的集合时,我们提到,set是一个“罐子”。我们可以向其中放入各式各样的元素,这些元素没有顺序,但不能相同。其中,HashSet是最常用的一个实现类。...
  • u010066934
  • u010066934
  • 2016年02月19日 21:09
  • 2294

遍历数组并且删除其中某个元素

今天写课程设计的代码,在程序中有一部分代码涉及到遍历整个数组,并且检查数组中的每一个元素是否符合要求,对于不符号要求的数组元素删除之,刚开始的时候,就像平常写遍历数组的方式遍历,并且检查数组中的元素,...
  • Stephan14
  • Stephan14
  • 2016年01月09日 23:15
  • 3431
内容举报
返回顶部
收藏助手
不良信息举报
您举报文章:HashSet中消失的元素和多出来的元素
举报原因:
原因补充:

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