Object超类

Object超类

在Java中,如果一个类没有明确地指出超类,那么Object就是这个类的超类。实际上,Object类是所有类超类,这个类定义了一些重要的方法。

equals #

equals方法用来比较两个对象是否相等。不过,在Object类中,这是判断两个对象是否具有相同的引用

当对象引用ab指向同一个对象即认为a等于b,看起来,这似乎合乎情理,但是在很多情况下,需要比较对象的状态的相等性,所以,Object类的equals方法往往是没有什么用处的。

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

  1. 自反性

    :对于任何非空引用xx.equals(x)true
  2. 对称性

    :对于任何引用xy,当且仅当y.equals(x)true时,x.equals(y)才为true
  3. 传递性

    :对于任何引用xyz,若x.equals(y)truey.equals(z)true,那么x.equals(z)也应该为true
  4. 一致性

    :对于任何引用xy,若对象引用和equals方法未发生变化,那么多次调用应返回一致的结果;
  5. 对于任何非空引用xx.equals(null)false

参考之前 Employee类的equals方法:

 1class Employee {
 2    // skip...
 3    @Override
 4    public boolean equals(Object o) {
 5        if (this == o)
 6            return true;
 7        if (o == null || getClass() != o.getClass())
 8            return false;
 9        Employee employee2 = (Employee) o;
10        return Objects.equals(name, employee2.name)
11                && salary == employee2.salary;
12    }

这是一个典型的覆盖equals方法的策略:

  1. 检测othis是否同一引用,若是,则返回true
  2. 检测o是否为空,若为空,则返回false
  3. 检测othis是否是同一类型,若否,则返回false
  4. o转换为this
  5. 比较othis域的相等性

在导出类中调用equals方法时,要先调用基类的equals方法,基类的方法通过之后,再比较导出类的相关域的相等性。

参考如下例子:

 1class ExpClass extends Employee{
 2  // skip...
 3  @Override
 4    public boolean equals(Object o) {
 5      if(!super.equals(o)) return false;
 6
 7      // super.equals checked that o and this belong to same class
 8      ExpClass m = (ExpClass)o;
 9      return bonus == m.bonus;
10    }
11}

以上的2个equals方法说明的是若导出类拥有自己的相等概念,那么在第3步类型判断中必须使用getClass,这样,基类和导出类(或者不同导出类)之前必然是不等的。

考虑一种情况:若想在基类和导出类之间(或不同导出类之间)进行相等比较,那么只需要比较基类共有的域即可,即是否相等由基类判断,该如何处理呢?

参考如下例子:

 1public class EqualsT {
 2
 3    public static void main(String[] args) {
 4        Stu a = new Stu("ali", 18, "190410");
 5        StuM b = new StuM("ali", 18, "190410", "班长");
 6        System.out.println(a.equals(b));
 7        System.out.println(b.equals(a));
 8    }
 9}
10
11class Stu {
12    protected String name;
13    protected int age;
14    protected String code;
15
16    public Stu(String name, int age, String code) {
17        this.name = name;
18        this.age = age;
19        this.code = code;
20    }
21
22    @Override
23    public final boolean equals(Object o) {
24        if (this == o)
25            return true;
26        if (o == null)
27            return false;
28        if (!(o instanceof Stu))
29            return false;
30
31        Stu stu = (Stu) o;
32
33        if (age != stu.age)
34            return false;
35        if (name != null ? !name.equals(stu.name) : stu.name != null)
36            return false;
37        return code != null ? code.equals(stu.code) : stu.code == null;
38    }
39}
40
41class StuM extends Stu {
42    private String resp;
43
44    public StuM(String name, int age, String code, String resp) {
45        super(name, age, code);
46        this.age = age;
47        this.code = code;
48        this.name = name;
49        this.resp = resp;
50    }
51
52    // 导出类无法覆盖equals()方法
53}
54
55/*
56 * output:
57 * true
58 * true
59 */// :~

这个equals方法有几处变化:

  1. 这个方法是final
  2. 判断类型使用的是instanceof而非getClass

将方法声明为final即保证了基类对相等概念的控制权(导出类无法覆盖equals方法),这还不够,使用instanceof保证了基类和导出类(或不同导出类)之间域的相等性比较的可能

应该谨慎使用instanceof判断操作符号。

hashCode #

hash code (散列码)是由对象导出的一个整型值

Java语言对hashCode方法有如下规范

  1. 在同一次Java程序运行过程中,无论调用多少次对象的hashCode方法,返回的应该是同一个整型值;而在不同程序运行过程中则无此要求;
  2. 对于任何对象ab,若a.equals(b)true,那么ab的hashCode返回值应该相等;
  3. 对于任何对象ab,若a.equals(b)falseab的hashCode返回值没有必要一定不等;需要指出的是,给不同对象分配不同de hashCode值有利于提升哈希表的性能;

基于第2点规范,若重新定义了equals方法,那么必须重新定义hashCode方法

 1public class HashT {
 2    public static void main(String[] args) {
 3        String s = "s";
 4        String t = new String("s");
 5        StringBuilder sb = new StringBuilder(s);
 6        StringBuilder tb = new StringBuilder(t);
 7        System.out.println(s.hashCode() + " : " +sb.hashCode());
 8        System.out.println(t.hashCode() + " : " +tb.hashCode());
 9    }
10}
11
12/* output
13115 : 1956725890
14115 : 356573597
15*///:~

注意,st哈希值相同时因为String类覆盖了hashCode方法,其值是由字符串字面量值计算来的,而StringBuilder没有覆盖hashCode方法,其值是Object默认的hashCode方法导出的对象存储地址


参考: 再论Object超类