甲乙小朋友的房子

甲乙小朋友很笨,但甲乙小朋友不会放弃

0%

java学习笔记3-Object(equal/hashCode/toString)

Object特性

  • 所有类的超类
  • 可以引用任何对象

equals方法

Object类中的equals方法判断两对象是否有相同的引用

equals的重写

在实际coding中,一般我们都会对equals方法进行重写,例:

  1. 对于超类Employee

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    class Employee{
    ...
    public boolean equals(Object otherObject){
    //如果otherObject与this是同一个引用
    if(this == therObject) return true;

    //如果therObject为空
    if(therObject == null) return false;

    //如果类型不同
    if(getClass() != otherObject.getClass()) return false;

    //现在我们已经确认了otherObject是一个非空的Employee对象
    //为了比较实例域,我们将otherObject转换为Employee类型
    Employee other = (Employee) otherObject

    //检测实例域是否相等
    return name.equals(other.name)
    && salary == other.salary
    && hireDay.equals(other.hireDay);
    }
    }

    为了防止this.namethis.hireDay可能为null的情况,将最后一句改为:

    1
    2
    3
    return Obejcts.equals(name,other.name)
    && salary == other.salary
    && Objects.equals(hireDay,other.hireDay);

    Obejcts.equals(a,b)运行过程

    • 如果ab都为null,则返回true
    • 如果其中一个为null,返回false
    • 如果都不为null,则调用a.equals(b)
  2. 对于Employee的子类Manager

在子类中定义euqals方法时,要首先调用超类的euqals,然后比较子类中的实例域。

1
2
3
4
5
6
7
8
9
class Manager extends Employee{
...
public boolean equals(Object otherObject){
//先调用超类的equals方法,检验this与otherObject是否属于同一class
if(!super.equals(otherObject)) return false;
Manager other = (Manager) otherObejct;
return bonus == other.bonus
}
}

equals方法的特性

  • 自反性:若x!=null,则x.equals(x)应返回true
  • 对称性:y.equals(x)x.euqals(y)应返回同样结果
  • 传递性:若x.equals(y)返回true,且y.equals(z)返回true,则x.equals(z)也必须返回true
  • 一致性:若x与y的引用对象没有发生变化,则反复调用x.equals(y)应返回同样结果
  • 对任意非空引用x,则x.equals(null)应该返回false

相等测试

问题 如果隐式(this)和显式(传入的)的参数不属于同一类,equals方法如何处理?

解决方法一 在上例中,我们用到了getClass()

  //如果类型不同
        if(getClass() != otherObject.getClass()) return false;

解决方法二 有的程序员喜欢用instanceof检测: (instanceof 运算符是用来在运行时指出对象是否是特定类的一个实例)

if(!(otherObeject instanceof Employee)) return false;

方法一存在的问题e是一个Employee对象,m是一个Manager对象,且两者具有相同姓名、薪水和雇用日期。

如果Manager没有重新实现equals方法,那么当m1.equals(m2)比较时,就会使用到e.getClass() != m2.getClass()来进行比较,显然结果返回false,因此这不是正确的比较。

方法二存在的问题 那么对于e.equals(m)来说,instanceof返回true.(ManagerEmployee的一个实例) 但对于m.equals(e)来说,instanceof返回false

!!!!!违反了对称性!

总结

  • 如果子类拥有自己的相等概念,例如若两个Manager对象的姓名、薪水和雇用日期(父类Employee的域)均相等,而奖金(子类Manager的域)不相等,就认为两个Manager不相等。此时可以用getClass检测
  • 如果使用雇员ID(父类Employee的域)来作为相等检测标准,并这个标准适合所有的子类,就可以用instanceof检测,并将Employeeequals申明为final

完美的equals

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public boolean equals(Object otherObject){
//检测this与otherObject是否引用同一对象
if(this==otherObject)return true;
//检测otherObject是否为null
if(otherObject == null) return false;
//比较this与Object是否属于同一个类,有以下两种方案:
//如果equals的语义在每个子类中都有改变,就用getClass:
if(getClass() != otherObject.getClass()) return false;
//如果所有子类都有统一equals语义,则用instanceof检测:
if(!(otherObject instanceof ClassName)) return false;
//将otherObject转换为相应的类类型
ClassName other = (ClassName) otherObject
//对所需要的域进行比较
return field1 == other.field1
&& field2 == other.field2
&& Objects.equals(field3,other field3)
&& ...;
}

注意:如果在子类中重新定义equals,则要在其中包含调用super.equals(other)

hashCode方法

hashCode : 散列码

使用:

1
2
3
4
int hash = 0
for ( int i = 0 ; i < lenth() ; i++ ){
hash = 31 * hash + charAt(i)
}

注意

  • 每个对象都有一个默认的散列码,值为对象的存储地址
  • 字符串的散列码是由内容导出的,因此st有相同hashcode
  • 如果重新定义了equals方法,就必须重新定义hashCode方法,且通过equal测试的两个对象的hashCode应该也是相等的,以便用户可以将对象插入到散列表中
  • hashCode方法应该返回一个整型数值,并使得不同对象的hashCode更均匀。

例如:

1
2
3
4
5
6
7
class Employee{
public int hashCode(){
return 7 * name.hashCode()
+ 11 * new Double(salary).hashCode()
+ 13 * hireDay.hashCode();
}
}

更方便的方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
    public int hashCode(){
return Objects.hash(name,salary,hireDay);
}```


# toString方法

用途:返回表示对象值的字符串

例如:

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

特别地:

  1. 只要对象与一个字符串通过操作符“+”连接起来,或使用System.out.println(x),Java编译就会自动调用toString方法。
  2. Object类定义了toString方法,用来打印输出对象所属的类名和散列码。
  3. 有趣的是,数组继承了object类的toString方法。修正的方法是采用Array.toString.例如String s = Array.toString(luckyNumbers)。多维数组需要调用Array.deepToString方法。