Effective Java(二)

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

1. 覆盖equals时请遵守通用约定

不需要覆盖equals的情况:

  • 类的每个实例本质上都是唯一的。代表的是活动实体而不是值,例如Thread;

  • 不关心类是否提供逻辑相等的测试功能。如java.util.Random;

  • 父类已经覆盖了equals,从父类继承过来的行为对于子类也是合适的。例如:大部分的Set实现都从AbstractSet继承equals实现。

  • 类是私有的或者包级私有的,可以确定它的equals方法永远不会被调用

需要覆盖equals的情况:

       对于“值类(value class)”来说,它们需要判断“逻辑相等”,且超类还没有覆盖equals实现期望的行为,这时我们需要覆盖equals方法。
       值类:例如,IntegerDate,仅仅表示一个值,我们在调用equals时希望知道它们逻辑上是否相等,而不是想了解他们是否指向同一个对象。

覆盖equals应遵守的约定 (来自Object规范【JavaSE6】)

       自反性

       对于任何非null的引用值x,x.equals(x)必须返回true

       对称性

       对于任何非null的引用值x和y,如果x.equals(y)返回true,则y.equals(x)也必须返回true

       传递性

       对于任何非null的引用值x、y和z,如果x.equals(y)返回truey.equals(z)返回truex.equals(z)也必须返回true

       一致性

       对于任何非null的引用值x和y,只要equals方法内部比较所用到的字段内容没有被修改,那么多次调用x.equals(y)就会一致地返回true,或者false。

       非空性

       所有比较的对象都不为null,如下代码:

 @Override
public boolean equals(Object o){
    if(null == o){
        return false;
    }
......
}

      上述代码是不必要的。我们只需要用instanceof来判断参数类型,如果参数时null那么instanceof一定会返回false。同时instanceof检查是必须要进行的,因为假设参数是错误类型,又不进行instanceof检查将会抛出ClassCastException异常。因此,正确的非空性判断为:

@Override
public boolean equals(Object o){
    if(!(o instanceof MyClass)){
        return false;
    }
    MyType mt = (MyType) o;
......
}

       特别注意:不要将equals声明中的Object对象替换为其他的类型。原因是这个方法并没有覆盖Object.equals!参数类型不正确。在原有的equals方法的基础上,重载了一个强类型的equals方法。@Override注解在这里面就起作用了。

2. 覆盖equals时总要覆盖hashCode

       否则将会导致该类无法结合所有基于散列的集合一起正常工作(如HashMap、HashSet和HashTable)

覆盖hashCode应遵守的约定 (来自Object规范【JavaSE6】)

  • 在应用程序执行期间,只要对象的equals方法的比较操作所用到的信息没有被修改,那么对这同一对象调用多次,hashCode方法都必须始终如一地返回同一个整数。在同一个应用程序的多次执行过程中,每次执行所返回的整数可以不一致

  • 如果两个对象根据equals(Object)方法比较是相等的,则调用这两个对象中任意一个对象的hashCode方法都必须产生相同的整数结果

  • 如果两个对象根据equals(Object)方法比较是不相等的,hashCode不一定产生不同的整数结果

       特别注意:如果一个类是不可变的,且计算散列码的开销比较大,可以考虑把散列码缓存在对象内部,而不是每次请求都重新计算散列码。

3. 始终要覆盖toString

(1)当对象被传递给println、printf,字符串联操作符+、assert或者被调试器打印出来时,toString会自动调用

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

(3)无论是否指定格式,都用该在文档中明确表明你的意图;都应为toString返回中包含的所有信息,并提供一种编程式的访问途径。例如PhoneNumber类应该包含针对area code、prefix和line number的访问方法。

4. 谨慎地覆盖clone 

补充:Cloneable接口的用法

       Cloneable接口的目的是作为对象的一个mixin接口,表明这个对象允许克隆

       其主要的缺陷在于缺少一个clone方法,Object的clone方法是受保护的,如果不加借助于反射,就不能因为一个对象实现了Cloneable接口,就可以调用clone方法。

       这个接口的作用决定了Object中受保护的clone方法实现的行为:如果一个类实现了Cloneable,Object的clone方法就返回该对象的逐域拷贝,否则就会抛出CloneNotSupportException。

        这样的副作用是:无需调用构造器就可以创建对象

关于Clone方法应遵守的约定 (来自Object规范【JavaSE6】)

  • 创建和返回该对象的一个拷贝,这个拷贝的精确含义取决于该对象的类。
  • 一般来说:对于任何对象x,表达式x.clone() != x 为true 且 x.clone().getClass() == x.getClass() 为true,x.clone().equals(x)为true,但这些都不是绝对的要求。
  • 拷贝对象往往会创建它的类的一个实例,但它同时也会要求拷贝内部的数据结构,过程中没有调用构造器。

5. 考虑实现Comparable接口

补充:Comparable接口的用法

(1)compareTo方法没有在Object中声明,它是Comparable接口中唯一的方法。compareTo方法不但允许进行简单的等同性比较,而且允许执行顺序比较,它还是个泛型

(2)类实现了Comparable接口,就表明它的实例具有内在的排序关系,为实现了Comparable接口的对象数组进行排序可以直接Arrays.sotr(a)。一旦类实现了Comparable接口,它就可以跟许多泛型算法以及依赖于该接口的集合实现进行协作。事实上,Java平台类库中所有的值类都实现了Comparable接口。

(3)什么时候考虑实现Comparable接口:正在编写一个值类,它具有非常明显的内在排序关系,比如按照字母排序、数值排序或者年代排序,那就应该坚决考虑实现这个接口。

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

  • 实现者必须确保所有的x和y都满足:sgn(x.compareTo(y)) == -sign(y.compareTo(x))。也暗示着:当且仅当y.compareTo(x)抛出异常时,x.compareTo(y)抛出异常。
  • 实现者还必须确保这个比较关系是可传递的:(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))。如果违反这个条件请予以说明:“注意:该类具有内在的排序功能,但与equals不一致。”

(5)如果一个类有多个关键域,那么按什么样的顺序来比较这些域是非常关键的,必须从最关键的域开始,逐步进行到所有的重要域。

发布了35 篇原创文章 · 获赞 37 · 访问量 1万+

猜你喜欢

转载自blog.csdn.net/qq_34519487/article/details/103925305