• Java基础知识陷阱(六)


    本文发表于本人博客

        上次说了下equals跟==的问题,今天再来认识一下这个equals()跟hasCode()。上次的代码如下:

        class Person{
            public String name;
            public Person(String name){
                this.name = name;
            }
            public String getName(){
                return this.name;
            }
            @Override
            public boolean equals(Object anObject) {
                if (this == anObject) {
                    return true;
                }
                if (anObject instanceof Person) {
                    Person person = (Person)anObject;
                    if(person.name.equals(this.name)){
                        return true;    
                    }
                }
                return false;
            }
        }

    这样对于单纯的2个对象比较是可以达到要求的,但是我们知道在java中存在集合,比如HashSet、HashMap,这2个在判断上逻辑稍微是不同的,这个可以具体看看JDK源码,先看HashSet的add源码:

    public class HashSet<E>
        extends AbstractSet<E>
        implements Set<E>, Cloneable, java.io.Serializable
    {   
        static final long serialVersionUID = -5024744406713321676L;
        private transient HashMap<E,Object> map;
        private static final Object PRESENT = new Object();
        public HashSet() {
            map = new HashMap<E,Object>();
        }
        public boolean add(E e) {
            return map.put(e, PRESENT)==null;
        }
        ......
        .....
    }

    我们可以看到当我们使用HashSet对象的add方法的时候,其实其内部使用的是HashMap对象的put方法,而其value值是一个static final的常量,那么可以肯定的是无论增加多少个,那么其的value都是指向一个对象。下面我们再来看看HashMap的put方法:

        public V put(K key, V value) {
            if (key == null)
                return putForNullKey(value);
            int hash = hash(key.hashCode());
            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;
        }
        private V putForNullKey(V value) {
            for (Entry<K,V> e = table[0]; e != null; e = e.next) {
                if (e.key == null) {
                    V oldValue = e.value;
                    e.value = value;
                    e.recordAccess(this);
                    return oldValue;
                }
            }
            modCount++;
            addEntry(0, null, value, 0);
            return null;
        }
        static int hash(int h) {
            h ^= (h >>> 20) ^ (h >>> 12);
            return h ^ (h >>> 7) ^ (h >>> 4);
        }
        static int indexFor(int h, int length) {
            return h & (length-1);
        }

    从上面一些调用我们可以看到在put的时候要先去判断键是否为null,如果是的话putForNullKey()方法进行对整个table数组遍历,先从下标0开始如果对象的key为null就赋值替换掉并返回旧的key;不然就直接增加至最大的下标处。如果键不为null,那么这里就要先计算hashCode来散列来找出它的位置,如果位置上有对象就跟当前的对象对比相同就替换并返回旧value;不然就直接加入并返回null表示添加成功。

    这里我们就可以看到不管是HashSet还是HashMap增加对象的时候都有可能用到对象的hascode以及equals方法,所以上次我们单是重写了equals方法不能算是完美解决了,所以还需要重写hasCode方法,这次修改代码如下:

    class Person{
        public String name;
        public Person(String name){
            this.name = name;
        }
        public String getName(){
            return this.name;
        }
        @Override
        public boolean equals(Object anObject) {
            Boolean result = false ;
            if (this == anObject) {
                result = true;
            }
            if (anObject instanceof Person) {
                Person person = (Person)anObject;
                if(person.getName().equals(this.name)){
                    result = true;    
                }
            }
            System.out.println(result);
            return result;
        }
        @Override
        public int hashCode() {
            System.out.println("hashCode");
            return    this.name.hashCode();
        }
    }
        public static void main(String[] args) throws Exception {        
            Set<Person> set = new HashSet<Person>();
            set.add(new Person("www.luoliang.me"));
            set.add(new Person("luoliang.me"));
            set.add(new Person("www.luoliang.me"));
            
            for(Iterator<Person> iter = set.iterator();iter.hasNext();){
                System.out.println(iter.next().getName());    
            }     
        }

    运行上面的结果:

    hashCode
    hashCode
    hashCode
    true
    luoliang.me
    www.luoliang.me

    这样最后的输出,我们还可以推导出当HashSet<String>其原理:当向Set集合增加对象时,首先集合计算器计算要增加对象的hashCode,根据该值得到存放的位置。
          当改位置不存在对象时那么集合set认为该对象在集合中不存在直接增加进去;
          当改位置存在对象时,接着将准备增加到集合中的对象与改位置的对象进行equals比较,
            如果equals返回false,那么集合认为不存在该对象重新计算hashcode增加进去集合中;
            如果equals返回true时,集合认为该对象已经存在于集合中就不会再增加该对象到集合中
    所以当重写equals的时候必须重写hashCode方法!

    下面我们来总结下HashSet跟HashMap的关系:

        HashSet是采用HashMap来实现,key就是放进去的对象,value就是一个object常量不变的。
        Add方法的时候底层是往Map里put一个对象,其value是全部一样的,不关心的!
        HashMap里面有Map.Entry集合。而HashMap底层是用Entry数组的table来维护;
        Entry是实现MapEntry,而Entry里面又是维护key,value以及next
        调用add方法其实就是调用HashMap的put方法,这个put方法会对hascode以及exuals进行比较
            如果相同就替换并返回旧数据value;表示替换成功并未添加只是替换
            如果不同添加并返回null,表示添加成功!
        使用hascode来计算位置,不会随着set或者map的大小而改变检索对象的时间这样大大有利于提升性能。

    这次先到这里。坚持记录点点滴滴!


  • 相关阅读:
    几款免费的支持HTML5的音频视频转换软件推荐
    2 宽度优先爬虫和带偏好的爬虫(4)
    Hadoop源代码分析(三)
    Hadoop源代码分析(四)
    C# 收邮件
    关于Adobe flash palyer 安装出现的问题解决方案
    C#调用java类、jar包方法。
    EF 4.3 的一些基础使用
    .net数据库连接池问题:在同一页面使用一段时间后,提示超时,连接池不够用这类的提示!
    使用Google CDN的JSAPI服务来提供加载各类JS库的方法
  • 原文地址:https://www.cnblogs.com/luoliang/p/4157450.html
Copyright © 2020-2023  润新知