Object类是Java中所有类的始祖,在Java中每个类都是由它扩展而来的。如果没有明确的指出超类,Object 就被认为是这个类的超类。
在Java中,只有基本类型(primitive types)不是对象,例如,数值、字符和布尔类型的值都不是对象。
所有的数组类型,不管是对象数组还是基本类型的数组都扩展了 Object 类:

  1. Employee[] staff = new Employee[10];
  2. Object obj = staff;
  3. obj = new int[10];

equals

Object 类中的 equals 方法用于检测一个对象是否等于另外一个对象。在 Object 类中,这个方法将判断两个对象是否具有相同的引用。然而,对于多数类来说,这种判断并没有什么意义。可以重写 equals 来检测两个对象状态的相等性。
例如,如果两个雇员对象的姓名、薪水和雇佣日期都一样,就认为它们是相等的:

  1. public class Employee {
  2. ...
  3. public boolean equals(Object otherObject) {
  4. // a quick test to see if the objects are identical // 引用是否相等
  5. if (this == otherObject) return true;
  6. // must return false if the explicit parameter is null
  7. if (otherObject == null) return false;
  8. // if the classes don't match, they can't be equal
  9. if (getClass() != otherObject.getClass()) return false;
  10. // now we know otherObject is a non-null Employee
  11. Employee other = (Employee) otherObject;
  12. // test whether the fields have identical values
  13. return Objects.equals(name, other.name) && salary == other.salary && Objects.equals(hireDay, other.hireDay);
  14. }
  15. }

Objects.equals(name, other.name)name.equals(other.name) 要健壮不少。如果两个参数都为 null,Objects.equals(a,b) 调用将返回 true;如果其中一个参数为 null,则返回 false;否则,如果两个参数都不为 null,则调用 a.equals(b)
在子类中,就可以先调用超类的 equals ,再来比较子类中的实例域:

  1. public class Manager extends Employee {
  2. ...
  3. public boolean equals(Object otherObject) {
  4. if (!super.equals(otherObject)) return false;
  5. Manager other = (Manager) otherObject;
  6. // super.equals checked that this and other belong to the same class
  7. return bonus == other.bonus;
  8. }
  9. }

相等测试与继承:

Java 语言规范要求 equals 方法具有下面特性:

  1. 自反性:对于任何非空引用 x,x.equals(x) 应该返回 true。
  2. 对称性:对于任何引用 x 和 y,当且仅当 y.equals(x) 返回 true,x.equals(y) 也应该返回 true。
  3. 传递性:对于任何引用 x、y 和 z,如果 x.equals(y) 返回 true,y.equals(z) 返回 true,x.equals(z) 也应该返回 true。
  4. 一致性:如果x和y引用的对象没有发生变化,反复调用 x.equals(y) 应该返回同样的结果。
  5. 对于任意非空引用 x,x.equals(null) 应该返回false。

在 equals 方法中,前面的例子使用 getClass() 来处理类匹配:

  1. if (getClass() != otherObject.getClass()) return false;

还可以使用 instanceof 进行检测:

  1. if (!(otherObject instanceof Employee)) return false;

可以看到它违反了对称性,因为对于 Employee 对象 e 和 Manager 对象 m 来说,e.equals(m) 可以检测,但是反过来却不行。
而有些人认为使用 getClass() 违反了置换原则(里氏置换原则),因为 getClass() 限制了只能是相同类进行比较。有一个应用 AbstractSet 类的 equals 方法的典型例子,它将检测两个集合是否有相同的元素。AbstractSet 类有两个具体子类:TreeSet 和 HashSet,它们分别使用不同的算法实现查找集合元素的操作。无论集合采用何种方式实现,都需要拥有对任意两个集合进行比较的功能。
下面可以从两个截然不同的情况看一下这个问题:

  • 如果子类能够拥有自己的相等概念,则对称性需求将强制采用 getClass() 进行检测。
  • 如果由超类决定相等的概念,那么就可以使用 instanceof 进行检测,这样可以在不同子类的对象之间进行相等的比较。

在雇员和经理的例子中,只要对应的域相等,就认为两个对象相等。如果两个 Manager 对象所对应的姓名、薪水和雇佣日期均相等,而奖金不相等,就认为它们是不相同的,因此,可以使用 getClass() 检测。
但是,假设使用雇员的 ID 作为相等的检测标准,并且这个相等的概念适用于所有的子类,就可以使用 instanceof 进行检测,并应该将 Employee.equals 声明为 final。

hashCode

散列码(hash code)是由对象导出的一个整型值。散列码是没有规律的。如果 x 和 y 是两个不同的对象,x.hashCode() 与y.hashCode() 基本上不会相同。
由于 hashCode() 定义在Object类中,因此每个对象都有一个默认的散列码,其值为对象的存储地址。
如果重新定义equals 方法,就必须重新定义 hashCode 方法,以便用户可以将对象插入到散列表中。hashCode 方法应该返回一个整型数值(也可以是负数),并合理地组合实例域的散列码,以便能够让各个不同的对象产生的散列码更加均匀。equals 与 hashCode 的定义必须一致:如果 x.equals(y) 返回 true,那么 x.hashCode() 就必须与 y.hashCode() 具有相同的值。

  1. public class Employee {
  2. ...
  3. public int hashCode() {
  4. return Object.hash(name, salary, hireDay);
  5. }
  6. }

toString

绝大多数(但不是全部)的 toString 方法都遵循这样的格式:类的名字,随后是一对方括号括起来的域值:

  1. public class Employee {
  2. ...
  3. public String toString() {
  4. return getClass().getName() +
  5. "[name" + name +
  6. ", salary=" + salary +
  7. ", hireDay=" + hireDay +
  8. ']';
  9. }
  10. }

子类也可以定义自己的 toString 方法:

  1. public class Manager extends Employee {
  2. ...
  3. public String toString() {
  4. return super.toString() +
  5. "[bonus=" + bonus +
  6. "]";
  7. }
  8. }

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

在调用x.toString()的地方可以用””+x替代。这条语句将一个空串与x的字符串表示相连接。这里的x就是x.toString()。与toString不同的是,如果x是基本类型,这条语句照样能够执行。

需要注意的是数组直接继承了 Object 类的 toString 方法,所以打印数组会直接打印出 hash 值。