Effective Java读书笔记七
来源:互联网 发布:windows rt 越狱 编辑:程序博客网 时间:2024/05/24 15:42
Item 8:覆写equals方法应该遵守的准则
最简单的准则就是不覆写equals方法,这样就能避免很多问题。以下的这些情况不需要覆写equals方法:
- 类的每一个实例都是唯一的:比如Thread,我们关注的是它本身作为一个活动的实体,而不是一个值。
- 我们不关注某个类是否是逻辑相等的:比如java.util.Random类覆写了equals方法来检查两个Random的实例是否会产生相同的随机数,但是这个对我们来说基本毫无用处。
- 超类已经覆写了equals方法,并且该覆写对子类也适用:就像AbstractSet,AbstractList等类。
- 类的可见性是private或者package-private,并且我们确信没有人会调用它的equals方法。
当一个类需要使用对象ID以外的东西作为逻辑相等并且超类也没有提供相应的功能时,尤其是value classes,我们就需要覆写equals方法。当然,覆写equals方法不但需要满足我们逻辑相等的要求,而且对象的实例还需要满足map的key或者set的行为。不过我们在Item1里提到的控制对象实例的类不需要覆写equals方法。
根据Java规范,equals需要满足一下特性:
- 自反性(Reflexive):一个非null的对象x满足x.equals(x) == true;这个特性不大可能违反,所以不用详细讨论了。
- 对称性(Symmetric):非null对象x和y满足x.equals(y) == y.equals(x);这个特性非常容易违反,所以一定要谨慎。
- 传递性(Transitive):非null对象x、y和z,当x.equals(y)==true,y.equals(z)==true时,x.equals(z)==true;肯定会违反这个特性的情况是子类增加了某些字段影响了equals方法。比如我们有一个用来表示二维空间上点的类Point:
假设我们继承这个类,增加一个color属性:
现在问题就很麻烦了:新的子类ColorPoint怎么来覆写equals。如果不覆写的话是不会违反我们的这些特性的,但是直接使用Point的equals方法显然不适合子类。如果在ColorPoint中只比较ColorPoint类型,如:
这种写法直接违反了对称性,假如我们创建了两个Point:
那么p.equals(cp)是true,但是cp.equals(p)却为false。如果我们根据这个情况在ColorPoint的equals方法中加入对Point的判断,则又会违反传递性:
此时,当我们有三个Point的时候:
p1.equals(p2)为true,p2.equals(p3)也为true,但是p1.equals(p3)为false。
所以说,对于可以被实例化的类,当它的子类加入新的value属性时,我们无法为它覆写equals方法。
有人说可以用getClass方法代替instanceof,但是这么做实际上违反了LSP。因此,Joshua建议在这种情况下可以考虑对象的组合来代替类的继承,比如上面提到的ColorPoint,我们可以创建一个单独的类ColorPoint,它内部持有Point和Color对象,这样再覆写equals方法时就不会出现问题了。
值得注意的是在Java的标准库中也有这样有问题设计的类,比如java.sql.Timestamp继承了java.util.Date类,增加了一个nanoseconds属性,Timestamp类中的equals方法就违反了对称性。
另外,对抽象类的子类增加value属性不会违反equals的特性,因为抽象类不存在实例。 - 一致性(Consistent):非null对象x和y,只要x和y不改变,多次调用x.equals(y)所得到的结果是一样的;这个特性需要在immutable对象上着重注意一下,但是不管这个对象是否是immutable的,我们的equals方法都不能依赖不可靠的资源来做判断。比如java.net.URL的equals方法就依赖了URL对应的IP地址来比较,这显然是不靠谱的。当我们的网络链接出现问题时,这就会破坏掉equals的特性。
- 非null性(Non-nullity):对于任意非null对象x,x.equals(null)总是等于false;这个也很难违反。很多人喜欢在覆写equals方法时第一句判断参数是否为null,实际上我们的instanceof方法已经帮我们做了,所以这个判断可以不做。
千万不要违反上面的这些特性,否则程序就会出现难以调试的莫名错误。
最后,总结一下写出高质量equals方法的步骤:
- 如果参数引用的是对象自己,那么使用==操作符来比较,这样对性能也是帮助;
- 使用instanceof来判断equals传入的参数是否为正确的类型;
- 将参数转换为对应的类型;
- 比较对象本身和传入的参数,检查每个需要判断的字段:原始类型除了float和double,其它统统用==来比较;对于float则使用Float.compare而double则使用Double.compare;对于数组类型,应该把需要比较的元素依次拿出来做比较,如果需要全部元素比较的话,用Arrays.equals方法就好;至于引用类型,先判断是否为null,之后再判断是否相等。
关于比较的性能问题,最好先比较容易改动的字段,不要去比较那些和逻辑比较无关的东西,比如用来同步的Lock字段。另外,由关键字段计算得出的东西也不需要比较。 - 当完成equals的覆写后,问自己三个问题:是否满足对称性?是否满足传递性?是否满足一致性?当然,不能只问,要多写测试。
另外附加一些注意事项:
- 覆写equals方法的同时必须覆写hashCode
- 别省事,要写就踏踏实实的写
- 别把equals的参数类型改掉,记住原始equals的参数类型是Object,改掉就成重载了,覆写的时候方法前加上@Override。
- Effective Java读书笔记七
- Effective Java读书笔记(七)
- Effective Java 读书笔记(七):通用程序设计
- effective c++读书笔记(七)
- Effective Java读书笔记七:泛型(部分章节需要重读)
- Effective C++读书笔记之七
- effective C++读书笔记(七)
- 《Effective Java》读书笔记之一
- 《Effective Java》读书笔记
- Effective Java读书笔记
- Effective Java 读书笔记
- 《Effective Java》读书笔记
- 《Effective Java》读书笔记之一
- Effective java 读书笔记
- 《Effective in java》 读书笔记
- Effective Java读书笔记
- Effective java 读书笔记
- Effective Java读书笔记
- MFC下CSocket编程详解
- 文件空间映射mmap()函数
- “可选参数”趣事探轶
- poj 1032 Parliament 编程的小技巧
- 二分图最大多重匹配(poj 2289,poj 1698)
- Effective Java读书笔记七
- Jquery: 使用html生成的link的一个小问题。
- 大话设计模式 --- 卷首语
- C#索引器与索引属性_百度文库
- 4-12 编程基础-面向对象
- 【转】MFC疑难注解:CAsyncSocket及CSocket
- 【不抱怨21天】实践手册(第二天)—放下
- 给用户窗体控件ListBox列表框填充单元格数据的多种方法
- zoj 2229 贪心..