内容简介:不覆盖equals方法,类的每个实例都只与它自身相等。如果满足了以下任何一个条件,就正是所期望的结果:需要覆盖equals的情况:如果类具有自己特有的“逻辑相等”概念(不同于对象等同的概念),而且超类还没有覆盖equals以实现期望的行为。覆盖equals方法的时候需要遵守的通用约定(等价关系):
不覆盖equals方法,类的每个实例都只与它自身相等。如果满足了以下任何一个条件,就正是所期望的结果:
- 类的每个实例本质上都是唯一的。
- 不关心类是否提供了“逻辑相等”的测试功能。
- 超类已经覆盖了equals,从超类继承过来的行为对于子类也是合适的。
- 类是私有的或是包级私有的,可以确定它的equals方法永远不会被调用。
需要覆盖equals的情况:如果类具有自己特有的“逻辑相等”概念(不同于对象等同的概念),而且超类还没有覆盖equals以实现期望的行为。
覆盖equals方法的时候需要遵守的通用约定(等价关系):
- 自反性:对于任何非null的引用值x,x.equals(x)必须返回true;
- 对称性:对于任何非null的引用值x和y,当且仅当y.equals(x)返回true时,x.equals(y)必须返回true;
- 传递性:对于任何非null的引用值x、y、z,如果x.equals(y)返回true,并且y.equals(z)也返回true,那么x.equals(z)也必须返回true;
- 一致性:对于任何非null的引用值x和y,只要equals的比较操作在对象中所用的信息没有被修改,多次调用x.equals(y)就会一致地返回true,或者一致地返回false;
- 非空性:对于任何非null的引用值,x.equals(null)必须返回false。
里氏替换原则:一个类型的任何重要属性也将适用于它的子类型,因此为该类型编写的任何方法,在它的子类型上也应该同样运行得很好。
结合这些要求,实现高质量equals方法的诀窍:
- 使用==操作符检查“参数是否为这个对象的引用”。如果是则返回true。这是一种性能优化,如果比较操作有可能更昂贵,就值得这么做。
- 使用instanceof操作符检查“参数是否为正确的类型”。如果不是,则返回false。“正确的类型”是指equals方法所在的那个类,有些情况下,是指该类所实现的某个接口。
- 把参数转换成正确的类型。因为转换之前进行过instanceof测试,所以确保会成功。
-
对于该类中的每个“关键域”,检查参数中的域是否与该对象中对应的域相匹配。如果这些测试全部成功,则返回true,否则返回false;
- 对于既不是float也不是double类型的基本类型域,使用==操作符进行比较;
- 对于对象引用域,可以递归地调用equals方法;
- 对于float域,可以使用Float.compare方法;
- 对于double域,则使用Double.compare方法;对float和double域进行特殊的处理是必要的,因为存在着Float.NaN、-0.0f以及类似的double常量;
- 对于数组域,则要把以上这些知道原则应用到每个元素上。
- 编写完成了equals方法之后,要问三个问题:它是否是对称的、传递的、一致的?还要编写单元测试来检验这些特性。
还有一些告诫:
- 覆盖equals时总要覆盖hashCode;
- 不要企图让equals方法过于智能;
- 不要讲equals声明中的Object对象替换为其他的类型。
覆盖equals时总要覆盖hashCode
在每个覆盖了equals方法的类中,也必须覆盖hashCode方法。
Object规范:
- 在应用程序的执行期间,只要对象的equals方法的比较操作所用到的信息没有被修改,那么对这同一个对象调用多次,hashCode方法都必须始终如一地返回同一个整数。在同一个应用程序的多次执行过程中,每次执行所返回的整数可以不一致。
- 如果两个对象根据equals(Object)方法比较是相等的,那么调用这两个对象中任意一个对象的hashCode方法都必须产生同样的整数结果。
- 如果两个对象根据equals(Obejct)方法比较是不相等的,那么调用这两个对象中任意一个对象的hashCode方法,则不一定要产生不同的整数结果。但是给不相等的对象产生截然不同的整数结果,有可能提高散列表的性能。
如果hashCode方法为不相等的对象产生了很多相等的散列码,那么散列码相等的这些对象都被映射到同一个散列桶中,会是散列表退化成链表,极大的影响了散列表的性能。
一个好的散列函数通常倾向于“为不相等的对象产生不相等的散列码”。
始终要覆盖toString
在实际应用中,toString方法应该返回对象中包含的所有值得关注的信息。无论是否制定输出的格式,都应该在文档中明确地表明你的意图。无论是否指定格式,都为toString返回值中包含的所有信息,提供一种编程式的访问途径。
谨慎地覆盖clone
Cloneable接口的目的是作为对象的一个mixin接口,表明这样的对象允许克隆。但是它缺少一个clone方法,Object的clone方法是受保护的。Cloneable接口决定了Object中受保护的clone方法实现的行为:如果一个类实现了Cloneable,Object的clone方法就返回该对象的逐域拷贝,否则就会抛出CloneNotSupportedException异常。
如果实现Cloneable接口是要对某个类起到作用,类和它的所有超类都必须遵守一个相当复杂、不可实施的,并且基本上没有文档说明的协议,由此得到一种语言之外的机制:无需调用构造器就可以创建对象。
拷贝对象往往会导致创建它的类的一个新实例,但它同时也会要求拷贝内部的数据结构,这个过程没有调用构造器。
如果你覆盖了非final类中的clone方法,则应该返回一个通过调用super.clone而得到的对象。如果类的所有超类都遵守这条规则,那么调用super.clone最终会调用Object的clone方法,从而创建出正确类的实例。
实际上,clone方法就是另一个构造器;你必须确保它不会伤害到原始的对象,并确保正确地创建被克隆对象中的约束条件。
如果专门为了继承而去设计一个clone方法,那就应该模拟Object.clone的行为:它应该被声明为protected,抛出CloneNotSupportedException,并且该类不应该实现Cloneable接口。这样可以使子类具有实现或者不实现Cloneable接口的自由。还有,如果决定用线程安全的类实现Cloneable接口,那么要记得它的clone方法必须实现很好的同步。
Cloneable具有上述这么多的问题,可以肯定的说,其他的接口都不应该扩展这个接口,为了继承而设计的类也不应该实现这个接口,对于一个为了继承而设计的类,如果你未能提供行为良好的受保护的clone方法,它的子类就不可能实现Cloneable接口。
考虑实现Comparable接口
compareTo方法是Comparable接口中唯一的方法,compareTo方法不但允许进行简单的等同性比较,而且允许执行顺序比较。类实现了Comparable接口,就表明它的实例具有内在的 排序 关系。
一旦实现了Comparable接口,它就可以跟许多泛型算法以及依赖于该接口的集合实现进行协作。Java平台类库中的所有值类都实现了Comparable接口。如果你正在编写一个值类,它具有非常明显的内在排序关系,比如按字母排序、按数值顺序或者按年代排序,那就应该坚决考虑实现这个接口:
public interface Comparable<T> { int compareTo(T t); }
就好像违反了hashCode约定的类会破坏其他依赖于散列做法的类一样,违反compareTo约定的类也会破坏其他依赖于比较关系的类。依赖于比较关系的类包括有序集合类TreeSet和TreeMap,以及 工具 类Collections和Arrays,他们内部包含有搜索和排序算法。
CompareTo方法中域的比较是顺序的比较,而不是等同性的比较。比较对象引用域可以使通过递归地调用compareTo方法来实现。如果一个域没有实现Comparable接口,或者你需要使用一个非标准的排序关系,就可以使用一个显式的Comparator来代替,或者编写自己的Comparator,或者使用已有的Comparator。
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持 码农网
猜你喜欢:本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。
Django企业开发实战
胡阳 / 人民邮电出版社 / 2019-2 / 99.00元
本书以博客系统贯穿始末,介绍了Django的方方面面。书中共分四部分,第一部分介绍了正式进入编码之前的准备工作,内容包括需求分析、基础知识和Demo系统的开发;第二部分开始实现需求,内容涉及环境配置、编码规范以及项目结构规划,编写了Model层、admin页面、Form代码和View逻辑,引入了Bootstrap框架;第三部分重点介绍xadmin、django-autocomple-light和d......一起来看看 《Django企业开发实战》 这本书的介绍吧!
XML 在线格式化
在线 XML 格式化压缩工具
Markdown 在线编辑器
Markdown 在线编辑器