参考自–《Java核心技术卷1》
Object:所有类的超类
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()
方法。