对于所有对象都通用的方法

时间:2023-01-20 16:05:22

8.覆盖equals时请遵守通用规定

需要满足的条件: 

类的每个实例本质上都是唯一的。 
不关心类是否提供了“逻辑相等(logical equality)”的测试功能。 
超类已经覆盖了equals,从超类继承过来的行为对于子类也是合适的。 
类是私有的或是包级私有的,可以确定他的equals方法永远不会被调用。 


需要覆盖equals:如果类具有自己特有的“逻辑相等”概念,而且超类还没有覆盖equals以实现期望的行为,这时我们就需要覆盖equals方法。 

不需要覆盖equals:用实例受控确保“每个值至多只存在一个对象”的类。枚举类型就属于这种类。对于这样的类而言,逻辑相同与对象等同是一回事。 


equals方法实现等价关系通用约定(equivalence relation): 


自反性(reflexive):对于任何非null的引用值x,x.equals(x)必须返回true。 
如果违背,当你把该类的实例添加到集合(collection)中,然后该集合的contains会告诉你,没有包含你刚刚添加的实例。 

对称性(symmetric):对于任何非null的引用值x和y,当且仅当y.equals(x)返回true时,x.equals(y)必须返回true。 
一个实例对比不区分了大小写,但是,反过来对比是区分的,就导致了不对称: 

传递性(transitive):对于任何非null的引用值x、y和z,如果x.equals(y)返回true,并且y.equals(z)也返回true,那么x.equals(z)也必须返回true。 
子类增加的信息会影响到equals的比较结果。 

不错的权宜之计(workaround):使用复合。

一致性(consistent):对于任何非null的引用值x和y,只要equals的比较操作在对象中所用的信息没有被修改,多次调用x.equals(y)就会一致的返回true,或者一致的返回false。 
都不要是equals方法依赖于不可靠的资源。 

非空性(Non-nullity):对于任何非null的引用值x,x.equals(null)必须返回false。 


高质量equals方法: 

1、使用==操作符检查“参数是否为这个对象的引用”。 
2、使用instanceof操作符检查“参数是否为正确的类型”。 
3、把参数转换成正确的类型。 
4、对于该类的每个“关键(significant)”域,检查参数中的域是否与该对象中对应的域相匹配。 
这点myeclipse自动生成的并不完全完美,myeclipse生成如下: 
else if (!name.equals(other.name)) 
return false; 
但是,这条是要求你改成: 
else if (name != other.name && !name.equals(other.name)) 
return false; 
5、当你编写完成了equals方法之后,应该会问自己三个问题:他是否的对称的、传递的、一致的。 
(覆盖equals时总要覆盖hashcode;不要企图让equals方法过于智能;不要将equals声明中的Object对象替换为其他的类型。) 


9.覆盖equals时总要覆盖hashcode

每个覆盖了equals方法的类中,也必须覆盖hashCode方法。 
如果不这样的话,就会违反Object.hashCode的通用约定,从而导致该类无法结合所有基于散列的集合一起正常运作,这样的集合包括HashMap、HashSet和Hashtable。

在引用程序的执行期间,只要对象的equals方法的比较操作所用到的信息没有被修改,那么对这同一个对象调用多次,hashCode方法都必须始终如一的返回同一个整数。在一个应用程序的多次执行过程中,每次执行所返回的整数可以不一致。 
如果连个对象根绝equals方法比较是相等的,那么调用这两个对象中任意一个对象的hashCode方法都必须产生同样的整数结果。 
如果两个对象根据equals方法比较是不相等的,那么调用这两个对象中任意一个对象的hashCode方法,则不一定要蚕声不同的整数结果。但是程序员应该知道,给不相等的对象产生截然不同的整数结果,有可能提高散列表(hash table)的性能。(比如,当你一个entity只根据id比较是否相等,但是在没实例化之前,没有id数值,那么默认的equals返回false,但是hashCode返回的值却相等。) 
语言之外的(extralinguistic)机制:无需调用构造器就可以创建对象。 

如果你覆盖了非final类中的clone方法,则应该返回一个通过调用super.clone而得到的对象。 

实际上,对于实现了Cloneable的类,我们总是期望他也提供一个功能适当的公有的clone方法。 
通常情况下,除非该类的所有超类都提供了行为良好的clone实现,无论是共有的还是受保护的,否则,都不可能这么做。 


如果你想在一个类中实现Cloneable,首先他的超类都需要提供行为良好的clone方法。一个好的散列函数通常倾向于“为不相等的对象产生不相等的散列码”。 


必须排除equals比较计算中没有用到的任何域,否则很有可能违反hashCode约定的第二条。 

不要试图从散列码计算中排除掉一个对象的关键部分来提高性能。 

10.始终要覆盖toString

提供好的toString实现可以使类用起来更加舒适。 

在实际应用中,toString方法应该返回对象中包含的所有值的关注的信息。 

在实现toString的时候,必须要做出一个很重要的决定:是否在文档中指定返回值的格式。 

无论你是否决定指定格式,都应该在文档中明确的表明你的意图。 
如果你要指定格式,则应该严格的这样去做。 

无论是否指定格式,都为toString返回值中包含的所有信息,提供一种编程式的访问途径。 

11.谨慎的覆盖clone

Cloneable接口的目的是作为对象的一个mixin接口(mixin interface),表明这样的对象允许克隆(clone)。 
遗憾的是,他并没有成功的达到这个目的。其主要的缺陷在于,他缺少一个clone方法,Object的clone方法是受保护的。 
如果不借助于反射(reflection),就不能仅仅因为一个对戏那个实现了Cloneable,就可以调用clone。 

当被克隆的对象里面包含的域引用了可变的对象时: 
(实际上,clone方法就是另外一个构造器;你必须确保他能不会伤害到原始的对象,并确保正确的创建被克隆对象中的约束条件-invariant) 

clone架构与引用可变对象的final域的正常用法是不相兼容的。 
还要考虑在拷贝的过程中线程安全的问题,把被拷贝的对象设置为不可外部修改或者实现线程安全。 

最好提供某些其他的途径来代替对象拷贝,或者干脆不提供这样的功能。

另一个实现对象拷贝的好办法是提供一个拷贝构造器或拷贝工厂。

12.考虑使用comparable接口

compareTo方法并没有在Object中声明,它是Comparable接口中唯一的方法。compareTo方法不但允许进行简单的等同性比价,而且允许执行顺序比较,它与Object的equals方法具有相似的特征,它还是个泛型。

类实现了Comparable接口,就表明它的实例具有内在的排序关系

一旦类实现了Comparable接口,它就可以跟许多泛型算法以及依赖于该接口的集合实现协作。比如按字母顺序,按数值顺序或者按年代顺序,compareTo方法的通用约定于equals方法的相似:

将这个对象与指定的对象进行比较。当该对象小于、等于或大于指定对象的时候,分别返回一个负数、零或者正整数。如果由于指定对象的类型而无法与该对象进行比较,则抛出ClassCastException异常。

在下面的说明中,符号sgn(表达式)表示数学中的signum函数,它根据表达式的值为负值、零和正值,分别返回-1、0和1。

  • 必须确保所有的x和y都满足sgn(x.compareTo(y)) == -sgn(y.compareTo(x))。

  • 必须确保这个比较关系是可以传递的:(x.compareTo(y) > 0 && y.compareTo(z) > 0)暗示着x.compareTo(z) > 0。

  • 最后,必须确保x.compareTo(y) == 0 暗示着所有的z都满足sgn(x.compareTo(z)) == sgn(y.compareTo(z))

  • 强烈建议(x.compareTo(y) == 0) == (x.equals(y)),这个绝非必要,一般来说,任何实现了Comparable接口的类,若违反了这个条件,都应该明确说明。