effective java 读书笔记---第三章对于所有对象都通用的方法

20170409
8.覆盖 equals 方法需要遵守的约定
自反性:非空对象,自身与自身equals返回 true
对称性:非空对象a.equals(b) 与 b.equals(a)结果一致
传递性:非空对象 a与 b 相等 b 与 c 相等则 a 与 c 必然相等
一致性:非空对象 a.equals(b)的返回结果与方法调用次数无关(对象中的任何信息没有被修改)
非空性:任何与空对象比较时都应该返回 false
一般如下进行 equals 方法
使用==操作符判断对象是否为同一个对象(非必须,仅用来做优化,注意 null==null 会返回 true)
使用 instance of 判断对象是否为同一个对象(使用这个方法可以不做 null 校验,null对象instance of 必然返回 false,注意这一点只对一般对象有效,有时做特殊的子类校验时可能会比较同一超类下的不同子类,此时可以使用超类来做 instance of)
把参数转换成正确的类型(之前已经做过 instance of)
比较类中的每个关键域,优先比较那些可能不同的,和比较开销较小的域,对于对象引用的比较需要注意 null 校验field==null?o.field==null?field.equals(o.field) 如果 field 与o.field 通常是相同的对象引用则使用field==o.field||(field!=null&&field.equals(o.field)) 以提高效率
覆盖 equals 方法时还需要注意以下:
覆盖 equals 时总是需要覆盖 hashCode
不要企图让 equals 方法变得过于智能
不要讲 equals 方法中的 Object 参数转换成其他类型对象,此时 equals 只是重载而并没有覆盖原有的 equals 方法

9.覆盖 equals 方法必然要覆盖 hashCode 方法
如果不这样做,会使得该类无法结合基于散列的集合一起正常工作,例如hashMap,HashSet 等等
对象中每个关键域hashCode方法返回值可以按如下值:
boolean 返回 f?1:0
byte char short int 返回 (int)f
long 计算f^(f>>>32)
float floatToIntBits(f)
double doubleToLongBits(f)然后计算 long 的 hashCode
引用类型则使用某种范式来计算 hashCode 如果为 null 通常返回0
如果为数组则需要将每个元素当做单独的域来处理,Arrays.hashCode 来处理
再将上述所有散列结果 c按照如下公式合并到 resultresult = 31*result + c 返回 result 即为 hashCode 值
对于覆盖的 hashCode 方法必须保证 equals 返回 true 时返回相同的 hashCode 值,必须排除 equals 计算中没有用到的域
如果一个类是不可变得,且计算 hashCode 的开销很大,可以将 hashCode 的计算结果缓存在对象内部,可以在对象初始化时就计算 hashCode,也可以使用延迟加载,在第一次调用 hashCode 时计算
不要试图从散列码计算中排除对象的关键部分来提高性能,这样的话散列函数就会把实例映射到极少数的散列码上,造成基于散列码的集合将会显示出平方级的性能指标

10.始终覆盖 toString 方法

11.谨慎的覆盖 clone 方法
Cloneable接口的目的是为了表明对象可以被克隆,但它并没与提供克隆方法,Object 的 clone 方法是受保护的.Cloneable 接口的作用决定了 clone 方法的实现行为,如果实现了 Cloneable 接口 clone 方法就会返回该对象的逐域拷贝,否则就会抛出 CloneNotSupportedException,这是接口的一种非典型用法,不值得效仿
克隆方法会返回类的一个新的实例,同时也会要求拷贝类的内部数据,这一过程没有调用构造器
克隆方法就是另一个构造器,必须确保不伤害到原有对象,并正确的创建克隆对象中的约束条件
clone 与引用可变对象的final 域的正常使用是不互相兼容的(不能调用对象中 final 域的克隆方法)
与构造方法一样clone 不应该调用新对象中任何非final 的方法
覆盖 clone 方法也需要定义成 producted 并抛出CloneNotSupportedException,并且该类不应该实现 Cloneable 接口,可以使子类选择是否实现 Cloneable 接口
clone方法没有同步,因此可能需要编写同步的 clone 方法
所有实现了 Cloneable 接口的类都应该有一个共有方法覆盖 clone.该方法首先调用 super.clone,然后修正任何需要修正的域(拷贝任何包含内部深沉结构的可变对象,并用指向新对象的引用代替原来指向这些对象的引用),虽然这些内部拷贝操作往往可以使用递归调用 clone 来完成,但这并不是最佳方法.如果该类只包含基本类型的域,或者指向不可变对象的引用,那么多变情况是没有域需要修正(也有例外,例如代表序列号,id或者代表对象创建时间的域)
因此需要谨慎覆盖 clone 方法,如果扩展了一个实现了 cloneable 接口的类,那么除了实现一行为良好的 clone 方法之外,没有选择.否则最好提供某些其他途径来代替对象拷贝,或者干脆不提供这样的功能
另一个实现对象拷贝的好方法是提供一个拷贝构造器或拷贝工厂.拷贝构造器是一个构造器,它唯一的参数是包含该构造器的类public Test(Test test) 拷贝工厂类似于拷贝构造器的静态工厂public static Test newInstance(Test test) 这两种方法都比 clone 方法更具优势
因此对于一个为了继承而设计的类,如果没有提供行为良好的受保护的 clone 方法,它的子类就不可能实现 Colneable 接口

12.考虑实现Comparable 接口
实现了 Comparable 接口就表明它的实例具有内在的排序关系
compareTo 方法的通用约定与 equals 方法类似
将类对象与指定对象比较 小于 等于 大于时分别返回 负数 0 正数
compareTo方法与 equals 类似有自反性,传递性与对称性
compareTo强烈建议返回值为0时 equals 方法返回 true,但不是必须的,就是说在通常情况下 compareTo 需要与 equals 方法返回相同的结果,但如果一个类的 compareTo 与 equals 返回了不同的结果,它仍然能够正常工作,但如果一个有序集合包含了该类,这个集合可能无法遵循相应集合接口的通用约定,这是因为这些集合接口的通用约定是按照 equals 方法来定义的,但有序集合使用了 compareTo方法所施加的同等性测试,(例如,BigDecimal这两个方法就返回不一样的结果,一个BigDecimal(“1.0”)与 BigDecimal(“1.00”),他们的 equals 返回不相等,但 compareTo 返回相等,如果使用HashSet包含这两元素,集合中就会出现两个元素,但如果使用 TreeSet,集合中只会包含一个元素)
compareTo 方法通常先比较最重要的域,然后比较次重要的域,只要返回非零结果就立即返回代码如下:

    @Override
    public int compareTo(TestBigDecimal o)
    {
        if (x < o.getX())
            return -1;
        if (x > o.getX())
            return 1;
        if (y < o.getY())
            return y;
        if (y > o.getY())
            return y;
        return 0;

考虑到该方法只要求指定返回值得符号,该方法可以做如下优化:

    @Override
    public int compareTo(TestBigDecimal o)
    {
        int result = x - o.getX();
        if (result != 0)
            return result;
        return y - o.getY();
    }

这个技巧能够在这里工作的很好,但需要注意如下问题:使用这个方法必须确认最大与最小阈值的可能差必须小于或者等于Integer.MAX_VALU,否则就不能使用这个方法,因为一个有符号的32位整数还没有大到足以表达任意两个32位整数的差(如果i 是很大正整数,j 是很大负整数,那么 i-j 很可能将溢出并返回一个负值)

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值