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

一、覆盖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;
	}
	@Override
	public 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.Test
	public void test() {
		Map<PhoneNumber, String> m = new HashMap<PhoneNumber, String>();
		m.put(new PhoneNumber(707, 867, 5309), "Jenny");
		//取出结果为null
		System.out.println(m.get(new PhoneNumber(707, 867, 5309)));
	}
执行代码二:使用ArrayList执行结果输出为true

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

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

public V get(Object key) {

        Node<K,V> e;

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

    }

hash(key)源码如下:

static final 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

public int 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.Test
	public 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
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值