Effective Java阅读笔记(2)
Effective Java第3章 对于所有对象都通用的方法
8.覆盖equals时请遵守通用约定
覆盖Object.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,x,equals(null)必须返回false。
实现高质量equals方法的诀窍:
使用
==操作符检查“参数是否为这个对象的引用”。如果是,则返回true。这只不过是一种性能优化,如果比较操作有可能很昂贵,就值得这么做。使用
instanceof操作符检查“参数是否为正确的类型”。如果不是,则返回false。把参数转换成正确的类型。因为转换前进行过
instanceof测试,所以确保会成功。对于该类中的每个“关键”域,检查参数中的域是否与该对象中对应的域相匹配。如果这些测试全部成功,则返回
true,否则返回false。
对于既不是float也不是double类型的基本类型域,可以使用==操作符进行比较,对于对象引用域,可以递归地调用equals方法;对于float域,可以使用Float.compare方法;对于double域,则使用Double.compare。
9.覆盖equals时总要覆盖hashCode
Object.hashCode的通用约定:
在应用程序的执行期间,只要对象的
equals方法的比较操作所用到的信息没有被修改,那么对这同一个对象调用多次,hashCode方法都必须始终如一地返回同一个整数。在同一个应用程序的多次执行过程中,每次执行所返回的整数可以不一致。如果两个对象根据
equals(Object)方法比较是相等的,那么调用这两个对象中任意一个对象的hashCode方法都必须产生同样的整数结果。如果两个对象根据
equals(Object)方法比较是不相等的,那么调用这两个对象中任意一个对象的hashCode方法,则不一定要产生不同的整数结果。但是程序员应该知道,给不相等的对象产生截然不同的整数结果,有可能提高散列表的性能。
10.始终要覆盖toString
提供好的toString实现可以使类用起来更加舒适,在实际应用中,toString方法应该返回对象中包含的所有值得关注的信息。
11.谨慎地覆盖clone
Clone方法的通用约定:
对于任何对象x,表达式x.clone() != x 将会是true,并且,表达式x.clone().getClass() == x.getClass()将会是true,但这些都不是绝对的要求。虽然通常情况下,表达式x.clone().equals(x)将会是true,但是,这也不是一个绝对的要求。拷贝对象往往会导致创建它的类的一个新实例,但它同时也会要求拷贝内部的数据结构。这个过程中没有调用构造器。
所有实现了Cloneable接口的类都应该用一个公有的方法覆盖clone。此公有方法首先调用super.clone,然后修正任何需要修正的域。一般情况下,这意味着要拷贝任何包含内部“深层结构”的可变对象,并用指向新对象的引用代替原来指向这些对象的引用。虽然,这些内部拷贝操作往往可以通过递归地调用clone来完成,但这通常并不是最佳方法。如果该类只包含基本类型的域,或者指向不可变对象的引用,那么多半的情况是没有域需要修正。这条规则也有例外,譬如,代表序列号或其他唯一ID值得域,或者代表对象的创建时间的域,不管这些域是基本类型还是不可变的,它们也都需要被修正。
另一个实现对象拷贝的好办法是提供一个拷贝构造器或拷贝工厂。拷贝构造器只是一个构造器,它唯一的参数类型是包含该构造器的类。如public Go(Go go);,拷贝工厂是类似于拷贝构造器的静态工厂public static Go newInstance(Go go);
12.考虑实现Comparable接口
compareTo方法的通用约定:
讲这个对象与指定的对象进行比较。当该对象小于、等于或大于指定对象的时候,分别返回一个负整数、零或者正整数。如果由于指定对象的类型而无法与该对象进行比较,则抛出ClassCastException异常。
在下面的说明中,符号sgn表示数学中的signum函数,它根据表达式的值为负值、零和正值,分别返回-1、0或1。
实现者必须确保所有的
x和y都满足sgn(x.compareTo(y)) == -sgn(y.compareTo(x))。实现者还必须确保这个比较关系是可传递的。
最后,实现者必须确保
x.compareTo(y) == 0暗示着所有的z都满足sgn(x.compareTo(z)) == sgn(y.compareTo(z))。强烈建议
(x.compareTo(y) == 0) == (x.equals(y)),但这并非绝对必要。一般说来,任何实现了Comparable接口的类,若违反了这个条件,都应该明确予以说明。推荐使用这样的说法:“注意:该类具有内在的排序功能,但是与equals不一致。”