ArrayList、HashSet的比较及Hashcode分析

来源:互联网 发布:层次软件体系结构 编辑:程序博客网 时间:2024/05/04 16:25

ArrayList、HashSet的比较及Hashcode分析
Collection、Set、List的区别如下:
-Collection各元素对象之间没有指定的顺序,允许有重复元素和多个null元素对象。
-Set各元素对象之间没有指定的顺序,不允许有重复的元素,最多允许有一个null元素对象。(HashSet)
-List各元素对象对象之间有指定的顺序,允许有重复的元素和多个null元素对象。(ArrayList)

hashCode方法与HashSet类
    如果想找到一个集合中是否包含有某个对象,大概的程序代码怎样写呢?你通常是逐一取出每个元素与要查找的对象进行比较,当发现某个元素与要查找的对象进行equals方法比较的结果相等时,则停止继续查找并返回肯定的信息,否则,返回否定的信息。如果一个集合中有很多个元素,譬如有一万个元素,并且没有包含要查找的对象时,则意味着你的程序需要从该集合中取出一万个元素来进行逐一比较才能得到结论。有人发明了一种哈希算法来提高从集合中查找原属的效率,这种方式将集合分成若干个存储区域,每个对象可以算出一个哈希码,可以将哈希码分组,每组分别对应某个存储区域,根据一个对象的哈希码就可以确定该对象应该存储在哪个区域,如图15.4所示。
哈希值对应区域n的对象


    HashSet就是采用哈希算法存取对象的集合,它内部采用对某个数字n进行取余的方式对哈希码进行分组和划分对象的存储区域。Object类中定义了一个hashCode()方法来返回每个Java对象的哈希码,当从HashSet集合中查找某个对象时,Java系统首先调用对象的hashCode()方法获取该对象的哈希码,然后根据哈希码找到相应的存储区域,最后取出该存储区域内的每个元素与该对象进行equals方法比较,这样不用遍历集合中的所有元素就可以得到结论。可见,HashSet集合具有很好的对象检索性能,但是,HashSet集合存储对象的效率相对要低些,因为向HashSet集合中添加一个对象时,要先计算出对象的哈希码和根据这个哈希码确定对象在集合中存放的位置。
    为了保证一个类的实例对象能在HashSet正常存储,要求这个类的两个实例对象用equals()方法比较的结果相等时,它们的哈希码也必须相等。也就是说,如果obj1.equals(obj2)的结果为true,那么以下表达式的结果也要为true:obj.hashCode() == obj2.hashCode()
    如果一个类的hashCode()方法没有遵循上述要求,那么,当这个类的两个实例对象用equals()方法比较结果相等时,它们本来应该无法被同时存储进Set集合中,但是如果将它们存储进HashSet集合中时,由于它们的hashCode()方法返回的值不同,第二个对象首先按照哈希码计算可能会被放进与第一个对象不同的区域中,这样它就不可能与第一个对象进行equals()方法比较了,也就可能被存储进HashSet集合中了。Object类中的hashCode()方法不能满足对象被存入到HashSet中的要求,因为它的返回值是通过对象内存地址推算出来的,同一个对象在程序运行期间的任何时候返回的哈希值都是始终不变的,所以只要是两个不同的实例对象,即使它们的equals方法比较结果相等,它们默认的hashCode方法返回的值是不同的。只要将例程15-3中使用的ArrayList集合改为使用HashSet集合就可以看到这种错误的效果了,修改后的代码如例程15-5所示。
import java.util.HashSet;

class Ch15_Demo5{
      public static void main(String[] args){
  HashSet users = new HashSet();
  
  users.add(new User("张三",28));
  users.add(new User("李四",25));
  users.add(new User("王五",31));
  System.out.println(users.size());
  
  users.remove(new User("张三",28));
  System.out.println(users.size());
 }
}
例程15-5运行的结果为:在删除元素之前打印出的集合中的元素个数是3,删除元素后再次打印出的集合中的元素个数还为3,这个结果显然是错误的。这是因为例程15-4中定义的User类虽然覆盖了equals()方法,但是没有覆盖hashCode()方法,当两个User实例对象的equals方法比较的结果相等时,而这两个对象的哈希码却不同,所有导致它们存储进HashSet中的存储区域不同。
public int hashCode(){
 return name.hashCode()+age;
}
    在例程15-6定义的User类的基础上,再次运行例程15-5,在删除元素后再打印出的集合中的元素个数变成为2了,这说明成功地从HashSet集合中删除了一个对象。!!!只有类的实例对象要被采用哈希算法进行存储和检索时,这个类才需要按照要求覆盖hashCode方法。即使程序可能暂时不会用到当前类的hashCode方法,凡是为它提供一个hashCode方法也不会有什么不好,没准以后什么时候又用到这个方法了,所以,通常要求hashCode方法和equals方法一并被同时覆盖。
    提示:
 (1)通常来说,一个类的两个实例对象用equals()方法比较的结果相等时,它们的哈希码也必须相等,但反之则不成立,即equals方法比较结果不相等的对象可能有相同的哈希码,或者说哈希码相同的两个对象的equals方法比较的结果可以不等,例如,字符串"BB"和"Aa"的equals方法比较结果肯定不相等,但它们的hashCode方法返回的值却相等。
 (2)当一个对象被存储进HashSet集合中以后,就不能修改这个对象中的那些参与计算哈希值的字段了,否则,对象修改后的哈希值与最初存储进HashSet集合中的哈希值就不同了,这种情况下了,即使在contains方法使用该对象的当前引用作为参数去HashSet集合中检索对象,也将返回找不到对象的结果,这也会导致无法从HashSet集合中单独删除当前对象,从而造成内存泄露。

验证以上说法的测试代码:
package lqq.heima;

import java.util.ArrayList;
import java.util.Collection;
import java.util.HashSet;

public class ReflectTest2 {

 /**
  * @param args
  */
 public static void main(String[] args) {
  // TODO Auto-generated method stub
  //Collection collections = new ArrayList();//size is 4
  Collection collections = new HashSet();//size is 2
  ReflectPoint pt1 = new ReflectPoint(3,3);
  ReflectPoint pt2 = new ReflectPoint(5,5);
  ReflectPoint pt3 = new ReflectPoint(3,3);
  collections.add(pt1);
  collections.add(pt2);
  collections.add(pt3);
  collections.add(pt1);
  //pt1.y = 7;//不修改对象对应的hash成员值可以成功remove
  //pt1.y = 7;
  collections.remove(pt1);//size is 2 ,修改了hashCode对应值的会改变对象的hashCode值导致remove的时候找不到对象去remove
  System.out.println(collections.size());
  

 }

}

注意:这个地方的collections.remove(pt1)功能是:
移除此列表中首次出现的指定元素(如果存在)。如果列表不包含此元素,则列表不做改动。更确切地讲,移除满足 (o==null ? get(i)==null : o.equals(get(i))) 的最低索引的元素(如果存在此类元素)。如果列表中包含指定的元素,则返回 true(或者等同于这种情况:如果列表由于调用而发生更改,则返回 true)。