Effective Java读书笔记(第3章-对于所有对象都通用的方法)

来源:互联网 发布:javascript编程精粹 编辑:程序博客网 时间:2024/05/16 14:33

       第3章  对于所有对象都通用的方法

       第8条:覆盖equals时请遵守通用约定

      1.  类的每个实例本质上都是唯一的

      2. 不关心类是否提供了“逻辑相等”的测试功能

      3. 超类已经覆盖了equals,从超类继承过去的行为对于子类也是合适的

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


      那么什么时候覆盖Object.equals呢?如果类具有自己特有的“逻辑相等”概念(不同于对象等同的概念),而且超类还没有覆盖equals以实现期望的行为,这时我们就需要覆盖equals方法。这通常属于“值类(value class)”的情形。

      有一种值类不需要覆盖equals方法,就是枚举类型,因为它的每个值至多只存在一个对象。对于这样的类,逻辑相同与对象等同是一回事。

      1. == 的例子

import java.util.ArrayList;import java.util.List;public class Equals {public static void main(String[] args) {List<String> list1 = new ArrayList<String>();List<String> list2 = new ArrayList<String>();list1.add("a");list1.add("b");list2.add("a");list2.add("b");System.out.println(list1 == list2);}}

      结果是false。

      2. 重写equals方法的例子

import java.util.ArrayList;import java.util.List;public class Equals {public static void main(String[] args) {List<String> list1 = new ArrayList<String>();List<String> list2 = new ArrayList<String>();list1.add("a");list1.add("b");list2.add("a");list2.add("b");System.out.println(list1.equals(list2));}}

      结果是true。

      3. 没有覆盖equals方法。

      首先创建一个Student类,这个类从Object类继承下来,equals方法没有被覆盖。代码如下:

public class Student {private String name;private String number;public String getName() {return name;}public void setName(String name) {this.name = name;}public String getNumber() {return number;}public void setNumber(String number) {this.number = number;}}

     创建两个不同的对象的情况:

public class Equals {public static void main(String[] args) {Student stu1 = new Student();Student stu2 = new Student();stu1.setName("stu");stu1.setNumber("1");stu2.setName("stu");stu2.setNumber("1");System.out.println(stu1.equals(stu2));}}

      比较的结果是false。

      引用指向同一对象的情况:

public class Equals {public static void main(String[] args) {Student stu1 = new Student();Student stu2 = stu1;stu1.setName("stu");stu1.setNumber("1");System.out.println(stu1.equals(stu2));}}

      结果是true。

      从以上得知,Object.equals方法是判断是两个引用是否指向同一对象,List覆盖了equals方法,所以实现了逻辑相等的判断。==也是判断是否是同一对象。


      在覆盖equals方法的时候,必须要遵守它的通用约定。以下是约定的内容,来自Object的规范:

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

      2. 对称性。对于任何非null的引用值x和y,当且仅当y.equals(x)返回true时,x.equals(y)必须返回true。

      3. 传递性。对于任何非null的引用值x, y, z,如果x.equals(y) 返回true,并且y.equals(z) 返回true,那么x.equals(z) 返回true。

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

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


     通过覆盖equals方法来比较Student的对象是否逻辑相等的例子如下:

public class Student {private String name;private String number;public String getName() {return name;}public void setName(String name) {this.name = name;}public String getNumber() {return number;}public void setNumber(String number) {this.number = number;}@Overridepublic boolean equals(Object o) {if(!(o instanceof Student))return false;Student stu = (Student)o;return (name == null ? stu.name == null : name.equals(stu.name)) && (number == null ? stu.number == null : number.equals(stu.number));}}

      以上是正确覆盖equals的方法。

      第9条:覆盖equals时总要覆盖hashCode

      Object.hashCode 的通用约定如下:

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

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

      3. 如果两个对象根据equals方法比较是不相等的,那么调用这两个对象中任意一个对象的hashCode方法,则不一定产生不同的整数结果。但是给出不同的结果,有可能提高散列表的性能。

      理想情况下,散列函数应该把集合中不相等的实例均匀地分布到所有可能的散列值上。以下给出一种简单的解决办法:

      1. 把某个非零的常数值,比如说17,保存在一个名为result的int类型的变量中。

      2. 对于对象中每个关键域f(指equals方法中涉及的每个域),完成以下步骤:

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

              i. 如果该域是boolean类型,则计算(f ? 1 :0)。

             ii. 如果该域是byte、char、short或者int类型,则计算(int)f。

             iii. 如果该域是long类型,则计算(int)(f ^ (f >>> 32))。

             iv. 如果该域是float类型,则计算Float.floatToIntBits(f)。

             v. 如果该域是double类型,则计算Double.doubleToIntBits(f),然后按照步骤2.a.iii,为得到的long类型计算散列值。

             vi. 如果该域是一个对象引用,并且该类的equals方法通过递归的调用equals的方式来比较这个域,则同样为这个域递归地调用hashCode。如果这个域的值为null,则返回0。

             vii. 如果该域是一个数组,则要把每一个元素当做单独的域来处理。用递归来处理,对每个重要的元素计算散列码,然后根据2.b中的做法吧这些散列值组合起来。

        b. 按照下面的公式,把步骤2.a中计算得到的散列码c合并到result中: result = 31*result + c;

     3. 返回result。

     4. 写完之后,测试是否符合以上3条约定。


     通过以上的方法,给Student类添加hashCode方法来覆盖hashCode,代码如下:

public class Student {private String name;private String number;public String getName() {return name;}public void setName(String name) {this.name = name;}public String getNumber() {return number;}public void setNumber(String number) {this.number = number;}@Overridepublic boolean equals(Object o) {if(!(o instanceof Student))return false;Student stu = (Student)o;return (name == null ? stu.name == null : name.equals(stu.name)) && (number == null ? stu.number == null : number.equals(stu.number));}@Overridepublic int hashCode() {int result = 17;result = 31*result + (number == null ? 0 : number.hashCode());result = 31*result + (name == null ? 0 : name.hashCode());return result;}}

     第10条:始终要覆盖toString

     Object提供的toString方法的一个实现,返回的是它的类名称,以及一个“@”符号,接着是散列码的无符号十六进制表示法。

     toString的通用约定是,被返回的字符串应该是一个“简洁的,但信息丰富,并且易于阅读的表达方式”,建议所有的子类都覆盖这个方法。

   

     第11条:谨慎地覆盖clone

    

     第12条:考虑实现Comparable接口

0 0
原创粉丝点击