Effective Java之对所有对象通用的方法

本文是《Effective Java》一书的整理笔记


1 equals 方法

1.1 以下几种情况不需要覆盖equals

  • 类的每个实例本质上是唯一的

    Object提供的equals的实现就已经足够了

  • 不关心类是否提供了 逻辑相等 的测试功能

  • 超类提供的equals的行为对子类也适合
  • 枚举类型,以及单例类
    对枚举类型而言,对象相同和逻辑相同是一回事

1.2 需要覆盖equals的几种情况

  • 类是私有的或者包级私有,可以确定equals不会被调用,这是应该覆盖equals防止被意外调用
@Override
public boolean equals(Object o){
    throw new AssertionError();
}
  • 如果类具有特有的逻辑相同(不同于 对象等同
    程序员在利用 equals方法来比较值对象时,希望知道它们在逻辑上是否相等,不想了解它们是否指向同一对象。

1.3 覆盖equals方法时的通用约定

  • 自反性 (reflexive)

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

  • 对称性 (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

  • 一致性 (consistent)

    对于任何非null的引用值x和y,只要equals的比较操作在对象中所用的信息没有被修改,多次调用x.equals(y)就会一致的返回true,或者一致的返回false

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

1.4 实现equals的方法

  • 使用==操作符检查 参数是否为这个对象的引用
  • 使用instanceof操作符检查 参数是否为正确类型

    所谓 正确类型 是指equals方法所在的那个类,有时指该类所实现的接口

  • 把参数转换成正确的类型

  • 对该类中的每个值进行比较

    • 对于非floatdouble的基本类型可以使用==操作符进行比较
    • 对于对象引用域,可以递归调用equals方法
    • 对于float可以使用Float.compare方法
    • 对于double可以调用Double.compare方法
    • 对于floatdouble的特殊值处理可以参考Float.equals的文档
    • 对于数组可以使用上述方式挨个比较,或者调用Arrays.equals方法
    • 为了防止有些对象引用域因为包含null而导致NullPointerException异常,通常采用以下方法比较
      (field == o.field) || (field != null && field.equals(o.field))
  • 优先比较最有可能不一致的域,提供性能

  • 不要将euqals声明中的Object对象替换为其他类型
  • 添加@Overide注解

2 覆盖equals时总要覆盖hashCode

2.1 hashCode规范

  • 在程序执行期间,只要equals所比较的信息没有被更改,对一个对象的多次调用,应当返回相当的散列码
  • 两个对象的equals方法比较是相等的,应当返回相同的散列码
  • 两个对象的equals方法比较是不相等的,不一定要返回不同的散列码,但是不同的散列码会提高散列表的性能

2.2 hashCode的计算方式

  • 选一个非0常数值,例如25,保存在int result变量中
  • 对于equals方法涉及的每个域,完成以下步骤

    为该域计算int类型的散列码c:

    • 如果为boolean类型,则计算 f ? 1 : 0
    • 如果为bytecharshort或者int类型,则计算 (int)f
    • 如果为long类型,则计算 (int)(f ^ (f >>> 32))
    • 如果为float类型,则计算Float.floatToIntBits(f)
    • 如果为double类型,则计算Double.doubleToLongBits(f),然后再按long类型计算
    • 如果为对象引用,并且该类的equals方法递归的调用了equals方法来比较这个域,则同样为这个域递归的调用hashCode;否则需要对这个对象计算一个”范式”,针对这个”范式”调用hashCode。如果该对象为null,则返回0
    • 如果为数组,则把每个元素当成单独的域处理,或者使用Arrays.hashCode方法
  • 按照下列公式,将上述每步得到的散列码c合并到result

result = 31 * result + c;
  • 返回result

3 始终覆盖toString 方法

  • toString的输出应该是一个简洁的、信息富并且易于阅读的形式
  • 一旦指定了toString的输出格式,就必须坚持这种格式,因为有的程序员会编写相应的代码来解析这种字符串表示法。

4 谨慎覆盖clone 方法

  • Cloneable接口没有包含任何方法,唯一的作用就是实现该接口的对象调用clone()方法不会抛出异常,并且返回该对象的逐域拷贝。
    逐域拷贝意思就是创建新的对象,并且逐个拷贝原对象的所有成员变量的引用。虽然新拷贝对象内的变量与原对象内的变量不是同一变量,但他们指向相同的实例。实际上就是浅拷贝。
    Cloneable接口改变了 clone方法的行为,是接口的一种极端的用法,不值得效仿。
    使用 clone方法创建的对象,没有调用构造器

4.1 实现良好的clone行为

  • 使用公有的方法覆盖clone
  • 首先调用super.clone,获对象的拷贝
  • 如果该类包含需要修正的域,一般采用递归调用clone实现
    需要修正的域,意思是除了基本类型以及不可变类型之外的成员变量。代表序列化,或者唯一ID值的域,或者代表对象的创建时间的域,一般也需要修正
public class HashTable{
    private Object[] buckets
    @Override public HashTable clone() {
        try{
            HashTable result = (HashTable)super.clone();
            result.buckets = buckets.clone;
            return result;
        }catch(CloneNotSupportedException e) {
            throw new AssertionError();
        }
    }
}
  • clone方法内不要调用新对象中的任何非final方法

    如果clone调用了一个被覆盖的方法,那么在该方法所在的子类有机会修正它在克隆对象中的状态之前,该方法就会先被执行,这样很有可能会导致克隆对象和原始对象之间的不一致。

  • 在线程安全的类中实现clone方法,记得要做好同步。

4.2 clone的替代方式

使用clone方法克隆对象有着种种限制,最好使用构造器或者构造工程克隆对象。

public Yum(Yum yum);
public static Yum newInstance(Yum yum);

5 考虑实现Comparable接口

如果你编写一个值类,并且具有明显的内在排序关系,例如字母顺序,数值顺序,那么就该考虑实现Comparable接口

5.1 compareTo方法的通用约定

sgn会根据传入的值为负值、零值、正值,分别返回-1、0、1

  • 将这个对象与指定对象进行比较。当该对象小于、等于或大于指定对象的时候,分别返回一个负整数、零值、正整数。
  • 如果指定对象类型无法与该对象进行比较,则抛出ClassCastException异常
  • 必须满足 sgn(x.compareTo(y)) == -sgn(y.compareTo(x)),也就是说仅当x.compareTo(y)抛出异常时,y.compareTo(x)才抛出异常
  • 比较关系是可传递的(x.compareTo(y)>0 && y.compareTo(z)>0时,意味着x.compareTo(z)>0
  • 强烈建议(x.compareTo(y)==0)==(x.equals(y)),虽然并非绝对必要,但如果违反该条件应当予以说明:“注意,该类具有内在的排序功能,但与equals不一致

欢迎大家访问我的博客,转载请注明出处
http://blog.youkuaiyun.com/abyss521

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值