Object:所有类的超类

Object 类是Java中所有类的始祖,在Java中每个类都是由它扩展来的。

可以使用Object类型的变量引用任何类型的对象。

所有的数组类型,不管是对象数组还是基本类型的数组都扩展了Object类。

  C++注释:在C++中没有所有类的根类,不过,每个指针都可以转换成void* 指针。

1、equals方法

  在Object类中,equals方法用于判断两个对象是否具有相同的引用。Object类中源码如下:  

    public boolean equals(Object obj) { return (this == obj); } 

  如果两个对象具有相同的引用,它们一定是相等的。但是对于大多数类来说,这种判断没有什么意义,因为经常会需要判断两个对象的状态是否相等,状态相等就认为相等。下面给出编写一个完美equals方法的建议:  

  1)显示参数命名为 otherObject

  2)检测 this 和 otherObject 是否引用同一个对象:if  ( this == otherObject ) return true; 起到优化的作用

  3)检测 otherObject 是否为 null,如果为 null,返回 false:if (otherObject == null) return false;

  4)比较 this 与 otherObject 是否属于同一个类,如果 equals 的语义在每个子类中有所改变,即由子类的属性状态决定相等性,就使用 getClass 检测:if (getClass() != otherObject.getClass()) return false;如果所有的子类都拥有统一的语义,即由超类决定相等的概念,那么就使用 instanceof 检测:if (!(otherObject instanceof ClassName)) return false;

  5)将 otherObject 转换成相应的类类型变量:ClassName other = (ClassName) otherObject

  6)现在开始对所有需要比较的域进行比较了。使用==比较基本类型域,使用equals比较对象域。 return field1 == other.field1 && Objects.equals(field2,other.field2) && ...;如果在子类中重新定义equals,就要在其中包含调用super.equals(other)。


  提示:对于数组类型的域,可以使用Arrays.equals方法检测相应的数据元素是否相等。

顺便说一下:

  在Java中,instanceof 运算符的前一个操作符是一个引用变量,该变量为空则返回 false,后一个操作数通常是一个类(可以是接口),用于判断前面的对象是否是后面的类,或者其子类、实现类的实例。如果是返回 true,否则返回 false 。也就是说在使用 instanceof 关键字做判断时, instanceof  操作符的左右操作数必须有继承或实现关系。

  使用 Objects 类的 equals 方法 Objects.equals(field2,other.field2) 避免如果 field2 为 null 时,field2.equals(other.field2) 这种比较方法会产生异常,Objects 类在 java.util 包中,源码如下:

    public static boolean equals(Object a, Object b) { return (a == b) || (a != null && a.equals(b)); } 

  举个栗子:

 1 package com.song;
 2 
 3 import java.time.LocalDate;
 4 import java.util.Objects;
 5 
 6 public class Employee {
 7     private String name;
 8     private double salary;
 9     private LocalDate hireDay;
10 
11     public Employee() {
12 
13     }
14     public Employee(String name, double salary, int year, int month, int day) {
15         this.name = name;
16         this.salary = salary;
17         hireDay = LocalDate.of(year, month, day);
18     }
19 
20     //覆盖超类 Object 的 equals 方法
21     @Override
22     public boolean equals(Object otherObject) {
23         if (this == otherObject) return true;
24         if (otherObject == null) return false;
25 
26         if (getClass() != otherObject.getClass()) return false;
27 //        or
28 //        if (!(otherObject instanceof Employee)) return false;
29 
30         Employee other = (Employee) otherObject;
31 
32         /**
33          * 对于这里可以直接用other.name访问私有域解释
34          * 有一个原则是:一个方法可以访问所属类的所有对象的私有数据
35          * 即Employee类的方法可以访问Employee类的任何一个对象的私有域。
36          */
37         return Objects.equals(name, other.name)
38                 && salary == other.salary
39                 && Objects.equals(hireDay, other.hireDay);
40     }
41 
42     public static void main(String[] args) {
43         Employee empOne, empTwo = null;
44 
45         empOne = new Employee("song", 10, 2019, 5, 25);
46         System.out.println("empOne equals empTwo:" + empOne.equals(empTwo));
47 
48         empTwo = new Employee("song", 10, 2019, 5, 25);
49         System.out.println("empOne equals empTwo:" + empOne.equals(empTwo));
50 
51         System.out.println(empOne.hashCode());
52         System.out.println(empTwo.hashCode());
53 
54     }
55 
56 }
View Code 

运行结果:

empOne equals empTwo:false
empOne equals empTwo:true

子类中定义equals方法:

 1 package com.song;
 2 
 3 public class Manager extends Employee {
 4     private double bonus;
 5 
 6     @Override
 7     public boolean equals(Object otherObject) {
 8         if (!super.equals(otherObject))
 9             return false;
10         Manager other = (Manager) otherObject;
11         return bonus == other.bonus;
12     }
13 }
View Code

到这里基本说完了,还有Java语言规范要求的 equals 方法的 5 条特性,可以在API文档中 Object 类中 equals 方法查看。

2、hashCode方法

  散列码是由对象导出的一个整形值。Object类中定义的hashCode方法源码如下:

public native int hashCode();

  使用了native关键字,我看不懂具体方法实现,有时间看完 native关键字 这篇博客应该能明白一些,具体以后再说,暂时不常用。

  String 类使用下列算法计算散列码:

int hash = 0;
for (int i = 0; i < length(); i++)
    hash = 31 * hash + charAt(i);

  由于 hashCode 方法定义在 Object 类中,因此每个对象都有一个默认的散列码,其值为对象的存储地址。 使用 Object 类的这个方法可以用 Objects 的 hashCode 方法保证 null 安全。

  如果重新定义 equals 方法,就必须重新定义 hashCode 方法,以便用户可以将对象插入到散列表中(散列表在核心技术I第9章讨论)。

  hashCode方法应该返回一个整形数值,并合理地组合实例域的散列码,以便能够让各个不同的对象产生的散列码更加均匀。例如,下面是 Employee 类的 hashCode 方法。

    @Override   //组合多个散列值,如果存在数组类型的域,使用静态的Arrays.hashCode方法计算一个散列码,equals与hashCode的定义必须一致
    public int hashCode() {
        return Objects.hash(name, salary, hireDay);
    }

  涉及的源码如下:

java.util.Objects
    public static int hash(Object... values) {
        return Arrays.hashCode(values);
    }

java.util.Arrays
    public static int hashCode(Object a[]) {
        if (a == null)
            return 0;

        int result = 1;

        for (Object element : a)
            result = 31 * result + (element == null ? 0 : element.hashCode());

        return result;
    }
View Code

  equals 与 hashCode 的定义必须一致:如果 x.equals(y)  返回 true,那么 x.hashCode() 就必须与 y.hashCode() 具有相同的值。例如,如果用定义的 Employee.equals 比较雇员的ID,那么 hashCode 方法就需要散列ID,而不是雇员的姓名或存储地址。

  先说到这吧,后面看了散列表再理解。

3、toString方法

   Object 中的一个重要方法,用于返回表示对象值的字符串。

  下面是 Employee 类中的 toString 方法的实现:

    @Override
    public String toString() {
        return getClass().getName()
                + "[name=" + name
                + ",salary=" + salary
                + ",hireDay=" + hireDay
                + "]";
    }

  下面是 Manager 类中的 toString 方法:

    @Override
    public String toString() {
        return super.toString() + "[bonus=" + bonus + "]";
    }

  随处可见toString方法的主要原因是:只要对象与一个字符串通过操作符 “+” 连接起来,Java 编译就会自动地调用 toString 方法,以便获得这个对象的字符串描述。

    提示:在调用 x.toString() 的地方可以用 ""+x 替代。这里的 x 就是 x.toString() 。与 toString 不同的是,如果 x 是基本类型,这条语句照样能够执行。

  System.out.println(x); println 方法就会直接调用 x.toString(x) ,并打印输出得到的字符串。

  Object 类定义了 toString 方法,用来打印输出对象所属的类名和散列码。例如 System.out.println(System.out); 语句输出 java.io.PrintStream@154617c ,因为 PrintStream 类没有覆盖 toString 方法。Object 类的toString方法源码如下:

    public String toString() {
        return getClass().getName() + "@" + Integer.toHexString(hashCode());
    }

    警告:令人烦恼的是,数组继承了 object 类的 toString 方法,数组类型将按照旧的格式打印,生成字符串如 “[I@1a46...”(前缀 [I 表明是一个整形数组)。修正的方式是调用静态方法 Arrays.toString,多维数组调用 Arrays.deepToString 方法。

  toString 方法是一种非常有用的调试工具。在标准类库中,许多类都定义了 toString方法, 以便用户能够获得一些有关对象状态的必要信息。强烈建议为自定义的每一个类增加 toString 方法。

 到这里基本把 Object 类的重要方法介绍了,后续有新东西会继续添加。

 程序清单: 

 1 package com.song;
 2 
 3 import java.time.LocalDate;
 4 import java.util.Objects;
 5 
 6 public class Employee {
 7     private String name;
 8     private double salary;
 9     private LocalDate hireDay;
10 
11     public Employee() {
12     }
13 
14     public Employee(String name, double salary, int year, int month, int day) {
15         this.name = name;
16         this.salary = salary;
17         hireDay = LocalDate.of(year, month, day);
18     }
19 
20     @Override   //覆盖超类 Object 的 equals 方法
21     public boolean equals(Object otherObject) {
22         if (this == otherObject) return true;
23         if (otherObject == null) return false;
24 
25         if (getClass() != otherObject.getClass()) return false;
26 //        or
27 //        if (!(otherObject instanceof Employee)) return false;
28 
29         Employee other = (Employee) otherObject;
30 
31         /**
32          * 对于这里可以直接用other.name访问私有域解释
33          * 有一个原则是:一个方法可以访问所属类的所有对象的私有数据
34          * 即Employee类的方法可以访问Employee类的任何一个对象的私有域。
35          */
36         return Objects.equals(name, other.name)
37                 && salary == other.salary
38                 && Objects.equals(hireDay, other.hireDay);
39     }
40 
41     @Override   //组合多个散列值,如果存在数组类型的域,使用静态的Arrays.hashCode方法计算一个散列码
42     public int hashCode() {
43         return Objects.hash(name, salary, hireDay);
44     }
45 
46     @Override
47     public String toString() {
48         return getClass().getName()
49                 + "[name=" + name
50                 + ",salary=" + salary
51                 + ",hireDay=" + hireDay
52                 + "]";
53     }
54 
55     public static void main(String[] args) {
56         Employee empOne, empTwo = null;
57 
58         empOne = new Employee("song", 10, 2019, 5, 25);
59         System.out.println("empOne equals empTwo:" + empOne.equals(empTwo));
60 
61         empTwo = new Employee("song", 10, 2019, 5, 25);
62         System.out.println("empOne equals empTwo:" + empOne.equals(empTwo));
63 
64         System.out.println(empOne.hashCode());
65         System.out.println(empTwo.hashCode());
66 
67         System.out.println(empOne);
68         System.out.println(empOne.toString());
69         System.out.println(empTwo.toString());
70 
71         Manager managerOne = new Manager("song",10,2019,5,25);
72         Manager managerTwo = new Manager("wang",100,2019,5,28);
73         System.out.println("empOne equals managerOne:" + empOne.equals(managerOne));
74         System.out.println("managerOne equals managerTwo:" + managerOne.equals(managerTwo));
75 
76         Manager managerThree = new Manager("song",10,2019,5,25);
77         System.out.println("managerOne equals managerThree:" + managerOne.equals(managerThree));
78 
79         System.out.println(managerOne.hashCode());
80         System.out.println(managerTwo.hashCode());
81 
82         System.out.println(managerOne);
83         System.out.println(managerOne.toString());
84         System.out.println(managerTwo.toString());
85         System.out.println(System.out);
86     }
87 
88 }
Employee.java
 1 package com.song;
 2 
 3 public class Manager extends Employee {
 4     private double bonus;
 5 
 6     public Manager(){
 7     }
 8 
 9     public Manager(String name, double salary, int year, int month, int day) {
10         super(name, salary, year, month, day);
11         bonus = 0;
12     }
13 
14     @Override
15     public boolean equals(Object otherObject) {
16         if (!super.equals(otherObject))
17             return false;
18         Manager other = (Manager) otherObject;
19         return bonus == other.bonus;
20     }
21 
22     @Override
23     public int hashCode() {
24         return super.hashCode() + 17 * new Double(bonus).hashCode();
25     }
26 
27     @Override
28     public String toString() {
29         return super.toString() + "[bonus=" + bonus + "]";
30     }
31 }
Manager.java

运行结果:

empOne equals empTwo:false
empOne equals empTwo:true
-1893166707
-1893166707
com.song.Employee[name=song,salary=10.0,hireDay=2019-05-25]
com.song.Employee[name=song,salary=10.0,hireDay=2019-05-25]
com.song.Employee[name=song,salary=10.0,hireDay=2019-05-25]
empOne equals managerOne:false
managerOne equals managerTwo:false
managerOne equals managerThree:true
-1893166707
-1683903746
com.song.Manager[name=song,salary=10.0,hireDay=2019-05-25][bonus=0.0]
com.song.Manager[name=song,salary=10.0,hireDay=2019-05-25][bonus=0.0]
com.song.Manager[name=wang,salary=100.0,hireDay=2019-05-28][bonus=0.0]
java.io.PrintStream@154617c

Process finished with exit code 0

  至于结果是两个不同类的对象生成的散列码是相同的这个情况,是否违背了上面说的 equals 与 hashCode 的定义必须一致的原则,下次再讨论。

原文地址:https://www.cnblogs.com/songbeyond/p/10931807.html