【effective Java读书笔记】对于所有对象都通用的方法(二)

来源:互联网 发布:淘宝邮票真假怎么鉴定 编辑:程序博客网 时间:2024/06/01 09:38

一、覆盖equals时总要覆盖hashCode

equals上节讲完之后,似乎比较两个对象的时候自己覆盖equals就非常好用了。然而,如果仅仅只是覆盖equals在HashMap中使用的时候会出现意料之外的结果。

如下代码:只覆盖了equals,没有覆盖hashCode方法。

public class PhoneNumber {private final short areaCode;private final short prefix;private final short lineNumber;public PhoneNumber(short areaCode, short prefix, short lineNumber) {super();this.areaCode = areaCode;this.prefix = prefix;this.lineNumber = lineNumber;}public PhoneNumber(int i, int j, int k) {this.areaCode = (short)i;this.prefix = (short)j;this.lineNumber = (short)k;}@Overridepublic boolean equals(Object obj) {if (obj == this) {return true;}if (!(obj instanceof PhoneNumber)) {return false;}PhoneNumber pn = (PhoneNumber) obj;return pn.areaCode==areaCode&&pn.prefix==prefix&&pn.lineNumber==lineNumber;}}
执行代码一:使用HashMap执行结果输出为null
@org.junit.Testpublic void test() {Map<PhoneNumber, String> m = new HashMap<PhoneNumber, String>();m.put(new PhoneNumber(707, 867, 5309), "Jenny");//取出结果为nullSystem.out.println(m.get(new PhoneNumber(707, 867, 5309)));}
执行代码二:使用ArrayList执行结果输出为true

@org.junit.Testpublic void test1() {List<PhoneNumber> m = new ArrayList<PhoneNumber>();m.add(new PhoneNumber(707, 867, 5309));//结果为trueSystem.out.println(m.contains(new PhoneNumber(707, 867, 5309)));}
执行代码一是没有put进去这个对象么?不。debug发现已经加入了HashMap;

然后看看问题出在哪?猜想可知出在HashMap的get方法,看看源码:

public V get(Objectkey) {

        Node<K,V>e;

        return (e = getNode(hash(key),key)) ==null ? null : e.value;

    }

hash(key)源码如下:

staticfinal int hash(Object key) {

        int h;

        return (key ==null) ? 0 : (h =key.hashCode()) ^ (h >>> 16);

    }

调用了HashMap的getNode方法;

//hash等于传入对象的hash值,key是指传入对象final Node<K,V> getNode(int hash, Object key) {//Node数组tab,Node值first        Node<K,V>[] tab; Node<K,V> first, e; int n; K k;        //table中数据给tab一份,first等于tab的最后一个值和hash值都不为空才等于tab的最后一个值;        if ((tab = table) != null && (n = tab.length) > 0 &&            (first = tab[(n - 1) & hash]) != null) {        //first.hash如果等于传入对象的hash值,才继续,如果再满足对象引用相等或者对象值相等返回first对象            if (first.hash == hash && // always check first node                ((k = first.key) == key || (key != null && key.equals(k))))                return first;            //如果first.next不为空            if ((e = first.next) != null) {                if (first instanceof TreeNode)                    return ((TreeNode<K,V>)first).getTreeNode(hash, key);                do {                //e.hash如果等于传入对象的hash值,才继续,如果再满足对象引用相等或者对象值相等返回e对象                    if (e.hash == hash &&                        ((k = e.key) == key || (key != null && key.equals(k))))                        return e;                } while ((e = e.next) != null);            }        }        return null;    }
上述源码写了详细的注释,判断这个对象new PhoneNumber(707, 867, 5309)是否在m中,断点发现第一个条件不满足:

(tab =table) != null && (n =tab.length) > 0 &&

            (first =tab[(n - 1) &hash]) !=null

然后再看上面加入的时候对象的hashCode是1766751238,而取出的时候hashCode是1174361318,导致两个相等对象不同的hashCode码。所以返回null。根据getNode的源码,发现HashMap自身还是有优化的,先去判断HashCode是否相等,如果不相等就不继续比较了,也就不存在equals方法比较了。棒棒哒!

解决这个问题非常好解决,只需要将对象相等的对象hashCode相等即可。在PhoneNumber中加入下面代码:

@Override

publicint hashCode() {

return 42;

}

返回一个固定值,那么是否很完美?不,这种算法那么就相当于将所有数据都塞入一个散列桶里了。将Hash的优化给干没了,性能也就相当于list的算法。

如何写出一个好的散列码呢?需遵守“为不相等的对象产生不相等的散列码”。

当然,此处我一般都用现代ide提供的自动生成HashCode方案。大概看一眼,String如何生成HashCode的方法,

 public int hashCode() {        int h = hash;        if (h == 0 && value.length > 0) {            char val[] = value;            for (int i = 0; i < value.length; i++) {                h = 31 * h + val[i];            }            hash = h;        }        return h;    }

二、始终要覆盖toString

至于问为什么?看个例子,

@org.junit.Testpublic void test2() {List<PhoneNumber> m = new ArrayList<PhoneNumber>();m.add(new PhoneNumber(707, 867, 5309));System.out.println(m.toString());}
如果没覆盖得到的结果:

[hashTest.PhoneNumber@2a]

如果覆盖得到的结果:

[PhoneNumber [areaCode=707, prefix=867, lineNumber=5309]]











阅读全文
0 0
原创粉丝点击