转载请注明来自:http://blog.youkuaiyun.com/huntersjm/article/details/43340257
这一章,主要是讲述了和Object对象相关的一些内容,包括,equals,hashCode,toString,clone,finalize,Comparable,compareTo方法
第八条:覆盖equals请遵守通用约定
一般来说,是不需要覆盖equals方法的,但是某些情况还是需要的,比如1:当一个类和另一个类,判断是否相同的标准是类中的某个成员变量是否相等。这种成员变量叫做,关键域。或者2:当一个类不允许被比较,则定义为私有的方法或者 3:两个类引用的地址完全一样才相等,即== 或者4:从父类继承过来的方法,不适用当前的类
简单的来说主要是:对象相等的逻辑与父对象的逻辑不同,这时便需要覆盖。
所谓遵守通用约定,那么有哪些呢?
学过离散数学,可能知道如下几点:
1:自反性 2:对称性 3:传递性 4:一致性 5:null不等性(暂且这么叫吧~)
所谓自反性,很简单,就是自己和自己肯定相等
所谓对称性,也很简单,A和B相等=》B和A相等
所谓传递性,就是A与B相等,B和C相等=》A也和C相等
所谓一致性,就是说A和B相等,只要A和B不变=》A和B一直相等
所谓null不等性,就是说对象肯定不等于null(注意,对象意味着本身不是null)
最常见的问题出现在,继承中,比如:一个正方形类A,另一个带颜色的正方形类B,继承了A,那么A a,B b两个对象,怎么比较相等呢?这本书就存在矛盾,是否需要考虑颜色,很多时候,很容易出现上述的问题。A类比较,只比较了边长,而B类还需要比如颜色,那么a.equals(b) 和 b.equals(a) 就不一定一样了。类似的问题还很多。所以Ej中推荐使用复合代替继承,就是说,将一个A a对象包含在B类中,就不存在这个问题了,因为A和B类肯定就不相等了。也不存在上述的问题。
总的说起来,有以下的方法来避免上述的问题:
1:使用==操作符来检查,是否引用相等,如果引用相等,那么肯定相等啊,是吧
2:使用instanceof来检查类型是否相等,特别地:null不属于任何类型
3:把参数转化为正确的类型,在进行比较(经过instanceof比较,这肯定没问题)
4:检查关键域,这里注意:按以下顺序比较比较合适 a.比较容易不一致的域,这样会提高性能,b.比较开销低的域,这样效率比较高 c.冗余域不需要比较,比如长方形类中的周长。
5:equals方法应该尽量遵守通用的约定
注意:容易出错的点
public boolean equals(Oval oval){
//...
}
如果使用这样的方式,是不行的,因为需要覆盖的方法是:equals(Object o) 而这里尝试覆盖的方法是Oval,实际上起的效果只是overload重载,提示,可以使用@Override来检查
第九条:覆盖equals方法时尽量覆盖hashcode方法
如果不覆盖hashcode方法可能会造成诸如HashMap,HashSet,Hashtable等类无法正常工作
Java中约定,1:只要equals用到的关键域没有被修改,那么hashCode值应该不变 2:如果equals返回true,那么两个对象的hashCode应该相同
举个例子证明按要求覆盖hashcode的重要性
如果我需要定义一个类,长方形类,而我们只关注其周长,我们认为周长相等的即相等
package com.sjm.hunter.effectivejava;
import java.util.HashMap;
import java.util.Map;
/**
* Created by jiamin.sjm on 15/1/31.
*/
public class Perimeter {
private int l;
private int w;
private int perimeter;
private boolean change;
public Perimeter(int l,int w){
this.l = l;
this.w = w;
this.change = true;
}
//获取周长
public int getPerimeter(){
if(this.change){
this.perimeter = 2*(this.l+this.w);
}
return this.perimeter;
}
//我们只关注周长
@Override
public boolean equals(Object o){
if(!(o instanceof Perimeter)){
return false;
}
return (this.getPerimeter() == ((Perimeter) o).getPerimeter());
}
public static void main(String []args){
Map<Perimeter,String> pers = new HashMap<Perimeter, String>();
pers.put(new Perimeter(2,3),"周长为10");
pers.put(new Perimeter(1,4),"周长为10");
System.out.println(new Perimeter(2,3).equals(new Perimeter(1,4)));
System.out.println(pers.size());
System.out.println(pers.get(new Perimeter(2,3)));
}
}
返回为:true 2 null (省略了回车)我们改造一下,增加:
@Override
public int hashCode(){
return this.getPerimeter();
}
那么返回值变成:true 1 周长为10
而如果将,equals去掉,那么会怎么样呢?返回为:false 2 null
EJ中又推荐的方法来获得hashCode(),这里不赘述,建议看原文
注意一定要将关键域用到,用且只用是最合适的
第十条:始终要覆盖toString()方法
很多时候,特别是调试的时候,经常需要看一个对象的具体内容,通常需要点开慢慢看,想想,如果可以直接将关键信息直接展示出来多好啊,类似字符串那样,而不是类名@hashCode的方式,是吧。
那么就覆盖toString()方法吧,把关键的信息展示出来,都不用那么麻烦的点点点,而且记录到日志也很方便了,既可以展示重要的信息,也不至于把隐私信息显示出来。
第十一条:谨慎地覆盖clone <不赘述>
clone()并不怎么使用,想到深拷贝,浅拷贝的问题,应该很容易理解这里的谨慎地覆盖,太容易出现无法预期的问题。
建议的方式是使用拷贝构造器或者拷贝工厂来获得(即方法的参数为源对象),同时如果加上参数,还可以更加灵活的使用该类
第十三条:考虑实现Comparabl接口
我想说,我用过很多次Comparable接口,当一个类的内部有很明确的顺序关系,同时外部需要调用到排序之类的,便需要实现该接口,同时该接口也需要和equals一样的,符合相应的原则,同时,鼓励当且仅当 : compare结果为0时,equals方法返回true
注意返回值:如果计算的结果超过int,可能符号会变得不一样,也就导致了错误的结果哦~