14.Object:所有类的超类【Java温故系列】

参考自–《Java核心技术卷1》


Object 类是 Java 中所有类的始祖,在 Java 中的每个类都是由它扩展而来。但不需要这样写:

public class Employee extends Object   // Employee类

如果没有明确地指出超类,Object 就被认为是这个类的超类。

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

Object obj = new Employee();

当然,Object 类型的变量只能作为各种值的通用持有者。要想对其中的内容进行具体的操作,还需要弄清楚对象的原始类型,并进行相应的类型转换:

Employee e = (Employee)obj;

在 Java 中,只有基本类型不是对象,例如:数值、字符和布尔类型的值都不是对象。

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

Employee[] staff = new Employee[10];
Object ob1 = staff;

int[] a = new int[10];
a[0] = 1;
Object ob2 = a;   // 注意ob2只是一个引用
System.out.println(ob.getClass().getSimpleName());  //输出此时ob2的类型

int[] b = (int[])ob;   //强制类型转换获得数组
System.out.println(b[0]);

1 equals 方法

Object 类中的 equals 方法用于检测一个对象是否等于另外一个对象。在 Object 类中,这个方法将判断两个对象是否具有相同的引用。如果两个对象具有相同的引用,它们一定是相等的。

在实际应用中,经常需要检测两个对象的相等性,如果两个对象的状态相等,就认为这两个对象是相等的。

通过下例演示 equals 方法的实现机制:

public class Employee{
    //域
	private String name;
	private double salary;
    //其他方法
	...
	public boolean equals(Object otherObject){
		if(this == otherObject)  
            return true;
        
        if(otherObject == null)
            //如果 otherObject 为 null
            return false;
        
        if(getClass() != otherObject.getClass())
            //如果不是同一个类的对象,getClass方法返回一个对象所属的类
            return false;
            
        //当下,other 不是空null,也是 Employee 对象
        Employee other = (Employee)otherObject;
        
        return Objects.equals(name,other.name)&&salary==other.salary;
	}
}

:比较两个对象时,需要考虑两个对象可能为 null 的情况,这时,可以使用 Objects.equals 方法。如果该方法的两个参数都为 null,则 Objects.equals(a,b) 调用将返回 true;如果其中一个参数为 null,则返回 false;否则,如两个参数都不为 null,则调用 a.equals(b)。

子类中定义 equals 方法时,首先调用超类的 equals 。如果检测失败,对象就不可能相等。如果超类中的域都相等,就需要比较子类中的实例域:

public class Manager extends Employee{
	priavte double bonus;  //经理的奖金域
    //其他方法
    ...
    public boolean equals(Object otherObject){
        if(!super.equals(otherObject))
            //调用超类的equals检测超类中的私有域是否相等
            return false;
        Manager other = (Manager)otherObject;
        return this.bonus == other.bonus;
    }
}

equals Api

  • static boolean equals(type[] a,type[] b)

    如果两个数组长度相同,并且在对应位置上数据元素也均相等,将返回true。

  • static boolean equals(Object a,Object b)

    如果 a 和 b 都是 null,返回 true;如果只有其中之一为 null,则返回 false;否则返回 a.equals(b).


2 相等测试与继承

如果隐式和显式的参数不属于同一个类,equals 方法是如何处理的呢?

上述的例子中,if(this.getClass() != otherObject.getClass()) 判断类是否匹配,若不匹配,则直接返回 false。

但很多人却喜欢使用 instanceof 进行检测:if(!(otherObject instanceof Employee)) .这样做不但没有解决 otherObject 是 Employee 的子类的情况,还可能会导致一些错误(不建议使用)。

Java 要求 equals 方法具有以下特性:

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

e.equals(m); 中,e 是一个 Employee 对象,m 是一个 Manager 对象,并且它们的共有域都相等。如果使用 if(!(other instanceof Employee)) 进行检测,则返回 true;但考虑对称性,意味着 m.equals(e); 也要返回 true。对称性不允许返回 false 或 抛出异常

那么,equals 的实现该用什么方式检测,有以下两种情况:

1.如果子类能够拥有自己的相等概念,则对称性需求将强制采用 getClass 进行检测

2.如果由超类决定相等的概念,那么就可以使用 instanceof 进行检测,这样可以在不同的子类对象之间进行相等的比较

如何编写出一个完美的 equals 方法的建议:

1)显示参数命名为 otherObject,稍后需要将它转换为另一个叫 other (ClassName类型)变量

2)检测 this(隐式参数)和 otherObject 是否引用同一个对象:

if(otherObject == this) return true;

这条语句只是一个优化。实际上,这是一个经常采用的形式,它比一个一个地比较类中的域开销小得多。

3)检测 otherObject 是否为 null,如果为 null 则返回 false。这项检测是很必要的。

if(otherObject == null)  return false;

4)比较 this 与 otherObject 是否属于同一个类。

如果 equals 的语义在每个子类中有所改变,就使用 getClass 检测:

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

如果所有的子类都有统一的语义,就使用 instanceof 进行检测:

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

5)将 otherObject 转换为当前类型变量(原本定义为 Object 变量):

ClassName other = (ClassName)otherObject; 

6)将所有需要比较的域进行比较。使用 == 比较基本类型域,使用 equals 比较对象域。如果所有的域都匹配,则返回 true;反之,返回 false。

7)如果在子类中需要重新定义 equals ,就需要在子类重新定义的 equals 方法中调用 super.equals(other)

:对于数组类型的域,可以使用静态的 Arrays.equals(a,b) 方法检测相应数组元素是否相等。

上述相关 Api:

  • Class getClass()

    返回包含对象信息的类对象

  • boolean equals(Object otherObject)

    比较两个对象是否相等,如果两个对象指向同一块存储区域,返回true;否则返回false。在自定义的类中,应该覆盖这个方法

  • String getName()

    返回这个类的名字

  • Class getSuperclass()

    以Class对象的形式返回这个类的超类信息


3 hashCode 方法

散列码(hash code)是由对象导出的一个整型值。散列码是没有规律的。如果 x 和 y 是两个不同的对象,x.hashCode() 与 y.hashCode() 基本上不会相同。

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

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

hashCode() 方法定义在 Object 类中,每个对象都有一个默认的散列码,其值为对象的存储地址。

String s = "OK";
StringBuilder sb = new StringBuilder(s);
System.out.println(s.hashCode()+"  "+sb.hashCode());
String t = new String("OK");
StringBuilder tb = new StringBuilder(t);
System.out.println(t.hashCode()+"  "+tb.hashCode());

在这里插入图片描述结果如左图。字符串 s 和 t 拥有相同的散列码(字符串的散列码由字符串内容得出);而字符串缓冲 sb 和 tb 的散列码却不同,这是因为在 StringBuilder 类中没有定义 hashCode 方法,它的散列码是 Object 类默认的 hashCode 方法导出的对象存储地址。

如果重新定义 equals 方法,就必须重新定义 hashCode 方法,以便用户可以将对象插入到散列表中。

hashCode 方法应该返回一个整型数值(可以为负数),并合理地组合实例域的散列码,以便能够让各个不同的对象产生的散列码更加均匀。

例如,Employee 类的 hashCode 方法:

public int hashCode(){
    //调用Object.hash并提供多个参数,hash方法会对各个参数调用Object.hashCode并组合这些值
    return Object.hash(name,salary);
}

equals 与 hashCode 的定义必须一致:如果 x.equals(y) 返回 true,则 x.hashCode() 就必须与 y.hashCode() 具有相同的值。

获取散列码的相关 Api:

  • int hashCode()

    返回对象的散列码

  • static int hash(Object obj1,Object…)

    返回一个由提供的所有对象的散列码组合的散列码

  • static int hashCode(Object a)

    如果 a 为 null 返回 0,否则返回 a.hashCode()

  • static int hashCode( int | long | short | byte | double | float | char | boolean value)

    返回给定值的散列码(多种基本类型)

  • static int hashCode(type[] a)

    计算数组 a 的散列码,数组的元素类型可以是object,int,long,short,char,byte,boolean,float,doubel


4 toString 方法

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

绝大多数的 toString 方法都遵循这样的格式:类的名字[域与域对应的值],如下

@Override
public String toString() {
	return "Employee{" +
    		"name='" + name + '\'' +
    		", salary=" + salary +
    		'}';
}

实际上,还可以设计的更好一些:通过调用 getclass().getName() 获得类名的字符(更加灵活),而不是将类名硬加到 toString 方法中:

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

toString 方法也可以供子类调用(若超类使用了 getClass().getName(),子类调用 super.toString 即可输出子类在超类中的域值):

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

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

:在调用 x.toString() 的地方可以直接用 “”+x 替代。这个 x 就是 x.toString() 。

如果 x 是一个任意一个对象,并调用:

System.out.println(x);

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

Object 类定义了 toString 方法,用于打印输出对象所属的类名和散列码。

:数组继承了 Object 类的 toString() 方法,所以数组的 toString 无法直接得到数组的值的输出(它会得到[I@4554617c类似的输出,前缀 [I 表明是一个整型数组 )。修正的方式是调用静态方法 Arrays.toString(),多维数组使用 Arrays.deepToString() 方法。

象,并调用:

System.out.println(x);

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

Object 类定义了 toString 方法,用于打印输出对象所属的类名和散列码。

:数组继承了 Object 类的 toString() 方法,所以数组的 toString 无法直接得到数组的值的输出(它会得到[I@4554617c类似的输出,前缀 [I 表明是一个整型数组 )。修正的方式是调用静态方法 Arrays.toString(),多维数组使用 Arrays.deepToString() 方法。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值