Effective Java 学习笔记 (五)

第七条      :在改写equals的时候请遵守通用约定

如果不改写equals方法,则每个实例只与它自己相等。我们同时也期望这样的情况:

1.  一个类的每个实例本质上都是惟一的。对于代表了活动实体而不是值的类,比如Thread

2.  不关心一个类是否提供了“逻辑相等”的测试功能。Random不需要比较两个随机数是否相等。

3. 超类已经改写了equals,从超类继承过来的行为对于子类也是合适的。

4.  一个类是私有的,或者是包级私有的,并且可以确定它的equals方法永远也不会被调用。这样的情况下,应该要改写equals,防止有一天它被调用到:

public boolean equals(Object o){

           throw new UnsupportedOperationException();

}

        

         当一个类有自己特有的“逻辑相等”概念(不同于对象身份的概念),而且超类也没有改写equals以实现期望的行为,这时我们需要改写equals方法。这通常适合于“值类value class”的情形。

         有一种值类可以不要求改写equals方法,即类型安全枚举类型typesafe enum21条)。因为安全枚举类型保证每个值至多只存在一个对象,所以对这样的情况而言,Object的实例相等等同于逻辑意义上的相等。

 

         改写equals方法必须遵守通用约定,来自java.lang.Object的规范:

         1自反性(reflexivex.equals(x)一定为true;

2对称性(symmetric)。当且仅当y.equals(x)返回true时,x.equals(y)返回true;

3传递性(transitive)。如果x.equals(y)返回true,并y.equals(z)也返回true,x.equals(z)也返回true

要想在扩展一个可实例化的类的同时,既要增加新的特性,同时还要保留equals约定,没有一个简单的办法可以做到这点。可考虑符合优先于继承(第14条)。

Java平台中,一些类是可实例化的子类,并加入了新特征,例如Java.sql.Timestampjava.util.Date进行子类化,增加了nanoseconds域。如果TimestampDate对象被用于同一个集合中,或者以其他方式被混合在一起,则会出现不正确的行为。

注意,你可以在一个抽象类的子类中增加新的特性,而不会违反equals的约定。对于用层次来代替联合而得到的一种类层次结构非常重要。只要不可能创建超类的实例,那么前面所述的种种问题都不会发生。

 

4一致性(consistent。多次调用x.equals(y),返回一致的值。

5对于任意的非空引用值xx.equals(null)一定返回fals

即不允许对象为空的情况抛出NullPointerException异常。但也不需要单独对null进行检查,可以使用instanceof操作符:

public boolean equals(Object o){

         if(!o instanceof MyType))

                   return false;

        

}

如果不检查类型,则会抛出ClassCastException异常,这违反了equals约定。但如果instanceof的第一个操作符是null的话,则会返回false

        

         实现高质量的equals方法:

1.  使用==操作符检查“实参是否为指向对象的一个引用”。这时一种性能优化。

2.  使用instanceof操作符检查“实参是否为正确的类型”。

3.  把实参转换到正确的类型。

4.  对于该类中每一个“关键”域,检查实参中的域于当前对象中对应的域是否匹配。

如果第二步中的类型是个接口,需要通过接口的方法,访问实参中的关键域。对于非floatdouble类型的原语类型域,可用==比较;对象引用域,可递归地调用equals方法;对floa域,可使用Float.floatToIntBits转换成int类型的值,然后用==比较int类型的值;对double域,可使用Double.doubleToLongBits转换成long类型的值,然后用==比较long类型的值;(考虑到Float.NaN-0.0f以及类似的double类型的常量);对于数组域,把以上的原则应用到每个元素上。对于某些对象引用域包含null是合法的:

(field==null ? o.field == null : field.equals(o.field))

如果fieldo.field通常是相同的对象引用,可用:

field == o.field || (field != null && field.equals(o.field))

         对于某些类,针对某个域的比较操作较复杂,应该在该类的规范上明确加以说明。如果这样,在每一种对象内部保存一个“范式”,这样equals方法可以根据范式进行低开销的精确比较。这项技术对非可变类最合适。

         域的比较顺序会影响equals方法的性能。所以最先比较最有可能不一致的域或者比较开销最低的域。一般不需要比较冗余域,但如果一个冗余域代表了这整个对象的一个概括描述,则当最终比较结果为false时,可省下比较实际数据所需的开销。

 

5.  当你编写完成了equal方法之后,应该问自己:是否对称、传递、一致的?(其他两个通常会自行满足)。

 

一些告诫:

a. 当改写equals的时候,总要改写hashCode

b.  不要企图让equals方法过于聪明。

c.  不要使equals方法依赖于不可靠的资源。

d.  不要将equals声明中的Object对象替换为其他的类型。因为这样并没有改写equals(Object o),而是重载了它。

 

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值