论坛首页 入门技术论坛

(十六):Set元素不能重复,重写equals方法就必须重写hashCode方法

浏览 3025 次
精华帖 (0) :: 良好帖 (0) :: 新手帖 (0) :: 隐藏帖 (0)
作者 正文
   发表时间:2009-02-28   最后修改:2009-03-03
public class People{
	String name;
	public People(String name){
		this.name = name;
	}
	public String getName(){
		return name;
	}
}
public class SetTest {
	public static void main(String[] args){
		Set<String> set = new HashSet<String>();//语句(1)
		System.out.println(set.add("abc"));//语句(2)
		System.out.println(set.add("xyz"));//语句(3)
		System.out.println(set.add("abc"));//语句(4)
		for(Iterator<String> it = set.iterator();it.hasNext();){//语句(5)
			System.out.println(it.next());
		}
		
		Set<People> st = new HashSet<People>();//语句(6)
		st.add(new People("zhangsan"));//语句(7)
		st.add(new People("lisi"));//语句(8)
		st.add(new People("zhangsan"));//语句(9)
		for(Iterator<People> it = st.iterator();it.hasNext();){//语句(10)
			System.out.println(it.next().getName());
		}
	}
}

问题1:执行完语句语句(1)(2)(3)(4)(5),打印结果是什么?为什么?
解答:执行完语句语句(1)(2)(3)(4)(5),打印结果是true,true,false,abc,xyz。因为String类重写Object类的equals和hashCode方法。
分析:Set是数学上的集合的集合的概念。关于数学上的集合它是不允许有重复元素的,举例:我有一个集合,元素是1、2、3、4、5,我在往集合里面增加元素3,这个集合的元素还是1、2、3、4、5,不会说是1、2、3、3、4、5。集合里的元素不允许有重复元素。java里面的Set完全反映到数学集合上的意义。java里面的Set里面的元素也是不运行重复的。在上述代码中有两个abc,是重复的。既然是重复的,你添加完一个再去添加另外一个相同的元素,是不能添加进去的。关于Set里面的一些方法,jdk文档是这样定义的:
boolean add(E e)
Adds the specified element to this set if it is not already present (optional operation). More formally, adds the specified element e to this set if the set contains no element e2 such that (e==null ? e2==null : e.equals(e2)). If this set already contains the element, the call leaves the set unchanged and returns false. In combination with the restriction on constructors, this ensures that sets never contain duplicate elements. (翻译:果 set 中尚未存在指定的元素,则添加此元素(可选操作)。更正式地说,如果此 set 没有包含满足下列条件的元素 e,则向 set 中添加指定的元素 o:(o==null ? e==null.equals(e))。如果此 set 已经包含指定的元素,则该调用不改变此 set 并返回 false。结合构造方法上的限制,这就可以确保 set 永远不包含重复的元素。)
The stipulation above does not imply that sets must accept all elements; sets may refuse to add any particular element, including null, and throw an exception, as described in the specification for Collection.add. Individual set implementations should clearly document any restrictions on the elements that they may contain.(翻译:上述规定并未暗示 set 必须接受所有元素;set 可以拒绝添加任意特定的元素,包括 null,并抛出异常,这与 Collection.add 规范中所描述的一样。每个 set 实现应该明确地记录对其可能包含元素的所有限制。)
也就是说,添加一个元素成功,会返回true,否则返回false.

问题2:执行完语句语句(6)(7)(8)(9)(10),打印结果是什么?为什么?
解答:执行完语句语句(6)(7)(8)(9)(10),打印结果是zhangsan,lisi,zhangsan。
分析:People类没有重写Object类的equals和hashCode方法,而Object类的equals方法比较的是对象的内存地址,Object类的hashCode方法返回的也是对象的内存地址,在语句(7)和语句(9)分别在堆里面生成了2个对象,它们的内存地址是不一样的。

问题3:为什么语句(4)中的abc添加不到Set中,而语句(9)中的第二个zhangsan能添加到Set中呢?
解答:学过数据结构就知道Hash是一个人的名字,在数据结构里面有Hash表Hash函数。在java里面对HashSet的实现也是使用Hash的方式去实现的。为什么用Hash的方式去实现呢?可以这样考虑:当如果不用Hash方式实现,还用正常方式实现,集合是不运行元素重复出现的,如果集合里已经有了1000个对象,这是我往集合里面增加第1001个对象,增加之前会把第1001个对象和集合里已经有的1000个对象一一比较,判断是否存在将要增加的对象,如果存在就不增加,如果不存在就增加,这样就存在效率问题。有十万个就要比较十万次,这么低的效率在我们现实开发里面是不允许的。java考虑到效率的问题没有按照正常的方式,而是采用了Hash的方式。HashSet的存储机制:Hash是散列,它是这样比较的,当往集合里面增加一个对象的时候,我们是把对象的引用(对象的内存地址)增加到集合里面去了,当把对象的引用增加到集合里面去后,集合要做什么呢?它首先读取增加对象的hashCode (调用对象的hashCode()方法,hashCode()方法在在Object类里面定义的),根据hashCode,集合去计算存放对象的位置,
当这个位置没有存放对象的话,集合就认为这个对象没有重复,直接就存放这个对象,这是第一种情况。第二种情况,首先它还是要计算hashCode,得到hash码,同样还是要计算位置,当它计算出位置时,发现这个位置已经有这个对象存在了,它并不会立刻告知增加不成功,它会接着调用你增加这个对象的equals方法跟已经在位置上存在的对象进行比较,如果equals方法返回true,表示这两个对象的内容是一样的,既然对象的内容是一样,集合就认为这个对象已经在集合里面存在了,就增加不进去了。集合的add方法返回一个false。如果equals方法返回false,表示将要增加的对象和集合里面已经存在的对象的内容不一样,既然内容不一样,集合就再去散列一次,计算出新的地址,把你这个对象放到新的地址上去,还是会增加成功。

问题4:怎样重写People类的equals和hashCode方法,而使语句(9)中的第二个zhangsan不能添加到Set中呢?
解答:代码如下:
@Override
	public boolean equals(Object obj) {
		if(obj == null) return false;
		if(this == obj) return true;
		if(obj instanceof People)
			if(obj.equals(this))return true;
		return false;
	}
	@Override
	public int hashCode() {
		return name.hashCode();
	}
总结:1、当向集合Set中增加对象时,首先集合计算要增加对象的hashCode码,根据该值来得到一个位置用来存放当前对象,挡在该位置没有一个对象存在的话,那么集合Set认为该对象在集合中不存在,直接增加进去。如果在该位置有一个对象存在的话,接着将准备增加到集合中的对象与该位置上的对象进行equals方法比较,如果该equals方法返回false,那么集合认为集合中不存在该对象,在进行一次散列,将该对象放到散列后计算出的新地址里,如果equals方法返回true,那么集合认为集合中已经存在该对象了,不会再将该对象增加到集合中了。
    2、重写equals方法的时候必须重写hashCode方法。如果一个类的两个对象,使用equals方法比较时,结果为true,那么该两个对象具有相同的hashCode。原因是equals方法为true,表明是同一个对象,它们的hashCode当然相同。
    3、Ojbect类的hashCode方法返回的是Object对象的内存地址。我们可以通过Integer.toHexString(new Object().hashCode);来得到。
   
论坛首页 入门技术版

跳转论坛:
Global site tag (gtag.js) - Google Analytics