5.2 Object:所有类的超类
Object类是Java中所有类的始祖,在Java中每个类都是由它扩展而来的,但是并不需要这样写:class Employee extends Object
如果没有明确地指出超类,Object就被认为是这个类的超类.
可以使用Object类型的变量引用任何类型的对象:
Object obj = new Employee("Harry", 3500);
当然,
Object类型的变量只能用于作为各种值的通用持有者.要想对其中的内容进行具体操作,还需要弄清楚对象的原始类型,并进行相应类型转换:Employee e = (Employee)obj;
在Java中,只有基本类型(primitive types)不是对象.例如,数值,字符和布尔类型的值都不是对象.所有的数组类型,不管是对象数组还是基本类型的数组都扩展于Object类.
Employee[] staff = new Employee[10];
obj = staff; // OK
obj = new int[10]; // OK
注释:在C++中没有所有类的根类,不过,每个指针都可以转换成 void* 指针.
5.2.1 equals方法
Object类中的equals方法用于检测一个对象是否等于另外一个对象.在Object类中,这个方法将判断两个对象是否具有相同的引用.如果两个对象具有相同的引用,它们一定是相等的.下面这个示例演示equals方法的实现机制.
class Employee
{
...
public boolean equals(Object otherObject)
{
// a quick test to see if the object are identical
if (this == otherObject)
return true;
// must return false if the explicit parameter is null
if (otherObject == null)
return false;
// if the classes don's match, they can't be equal
if (getClass() != otherObject.getClass())
return false;
// now we know otherObject is non-null Employee
Employee other = (Employee)otherObject;
// test whether the fields hava identical values
return name.equals(other.name) && salary == other.salary && hireDay.equals(other.hireDay);
}
}
getClass方法将返回一个对象所属的类.
提示:为了防备name或hireDay可能为 null 的情况,需要使用Object.equals方法.如果两个参数都为 null,Object.equals(a, b)调用将返回 true;如果其中一个参数为 null,则返回 false;否则,如果两个参数都不为 null,则调用a.equals(b).利用这个方法,Employee.equals方法的最后一条语句需要改写为:
return Objects.equals(name, other.name) && salary == other.salary && Objects.equals(hireDay, other.hireDay);
在子类中定义equals方法时,首先调用超类的equals .如果检测失败,对象就不可能相等.如果超类中的域都相等,就需要比较子类中的实例域.
class Manager extends Employee
{
...
public boolean equals(Object otherObject)
{
if (!super.equals(otherObject))
return false;
// super.equals checked that this and otherObject belong to the same class
Manger other = (Manager)otherObject
return bonus == other.bonus;
}
}
5.2.2 相等测试与继承
如果隐式和显式的参数都不属于同一个类,equals方法将如何处理呢?Java语言规范要求equals方法具有下面的特性:
1.自反性:对于任何非空引用x,x.equals(x)应该返回 true
2.对称性:对于任何引用x和y,当且仅当y.equals(x)返回 true,x.equals(y)也应该返回 true
3.传递性:如果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方法的建议:
1.显式参数命名为otherObject,稍后需要将它转换成另一个叫做other的变量
2.检测 this 与 otherObject是否引用同一个对象:
if (this == otherObject)
return true;
这条语句只是一个优化.实际上,这是一种经常采用的形式,因为计算这个等式要比一个一个地比较类中的域所付出的代价小得多.
3.检测otherObject是否为 null,如果为 null,返回 false .这项检测是必要的.
if (otherObject == null)
return false;
4.比较 this 与 otherObject是否属于同一个类.如果equals的语义在每个子类中有所改变,就使用getClass检测:
if (getClass() != otherObject.getClas())
return false;
如果所有的子类都拥有统一的语义,就使用 instanceof 检测:
if (!(otherObject instanceof ClassName))
return false;
5.将otherObject转换为相应的类类型变量:
ClassName other = (ClassName)otherObject;
6.对所有需要比较的域进行比较.使用==比较基本类型域,使用equals比较对象域.如果所有的域都匹配,就返回 true;否则返回 false .
return field1 == other.field1 && Objects.equals(field2, other.field2) && ...;
如果在子类中重定义equals,就要在其中包含调用 super.equals(other) .
提示:对于数组类型的域,可以使用静态的Arrays.equals方法检测相应的数组元素是否相等.
警告:下面是实现equals方法的一种常见的错误.
public class Employee
{
public boolean equals(Employee other)
{
return Objects.equals(name, other.name) && salary == other.salary && Object.equals(hireDay, other.hireDay);
}
}
这个方法声明的显式参数类型是Employee,其结果并没有覆盖Object类的equals方法,而是定义了一个完全无关的方法.
为了避免发生类型错误,可以使用@Override对覆盖超类的方法进行标记:
@Override public boolean equals(Object other);
如果出现了错误,并且正在定义一个新方法,编译器就会给出错误报告.
java.util.Array中的equals方法如下:
static Boolean equals(type[] a, type[] b)
如果两个数组长度相同,并且在对应的位置上数据元素也均相同,将返回 true,数组的元素类型可以是Object,int,long,short,char,byte,boolean,float,double .
java.util.Object中的equals方法如下:
static boolean equals(Object a, Object b)
如果a和b都为 null,返回 true;如果只有其中之一为 null,则返回 false;否则返回 a.equals(b) .
5.2.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);
如果重新定义equals方法,就必须重新定义hashCode方法,以便用户可以将对象插入到散列表中.hashCode方法应该返回一个整型数值,并合理地组合实例域的散列码,以便能够让各个不同的对象产生的散列码更加均匀.
例如,下面是Employee类的hashCode方法.
class Employee
{
public int hashCode()
{
return * name.hashCode() + 11 * new Double(salary).hashCode() + 13 * hireDay.hashCode();
}
}
不过Java中还可以做两个改进,首先,最好使用 null 安全的方法Objects.hashCode,如果其参数为 null,这个方法会返回0,否则返回对参数调用hashCode的结果.
public int hashCode()
{
return 7 * Objects.hashCode(name) + 11 * new Double(salarY).hashCode() + 13 * Objects.hashCode(hireDay);
}
还有更好的做法,需要组合多个散列值时,可以调用Objects.hash并提供多个参数.这个方法会对各个参数调用Objects.hashCode,并组合这些散列值.这样Employee.hashCode方法可以简单地写为:
public in hashCode()
{
return Objects.hash(name, salary, hireDay);
}
equals与hashCode的定义必须一致:如果x.equals(y)返回 true,那么x.hashCode()必须与y.hashCode()具有相同的值.
提示:如果存在数组类型的域,那么可以使用静态的Arrays.hashCode方法计算一个散列码,这个散列码由数组元素的散列码组成.
java.lang.Object的hashCode方法如下:
int hashCode()
返回对象的散列码.散列码可以是任意的整数,包括正数和负数.两个相等的对象要求返回相等的散列码.
5.2.4 toString方法
在Object中还有一个重要的方法,就是 toString方法,它用于返回表示对象值的字符串.Point类的toString方法将返回下面这样的字符串:java.awt.Point[x = 10, y = 20]
绝大多数的toString方法都遵循这样的格式:类的名字,随后是一对方括号括起来的域值.下面是Employee类中的toString方法的实现:
public Sring toString()
{
return "Employee[name = " + name + ", salary = " + salary + ", hireDay = " + hireDay + " ]";
}
实际上,最好通过调用getClass().getName()获得类名的字符串,而不要将类名加到toString方法中.
随处可见的toString方法的主要原因是:只要对象与一个字符串通过操作符"+"连接起来,Java编译器就会自动地调用toString方法,以便获得这个对象的字符串描述.例如:
Point p = new Point(10, 20);
String message = "The current position is " + p;
// automatically invokes p.toString()
提示:在调用x.toString()的方法可以用""+x替代.这条语句将一个空串与x的字符串表示相连接.这里的x就是x.toString(),与toString不同的是,如果x是基本类型,这条语句照样能够执行.
如果x是任意一个对象,并调用
System.out.println(x);
println方法就会直接地调用x.toString(),并打印输出得到的字符串.
toString方法是一种非常有用的调试工具.在标准类库中,许多类都定义了toString方法,以便用户能够获得一些有关对象状态的必要信息.像下面这样显式调试信息非常有益:
System.out.println("Current position = " + position);
更好的解决办法是:
Logger.global.info("Current position = " + position);
提示:强烈建议为自定义的每一个类增加toString方法.这样做不仅自己受益,而且所有使用这个类的程序员也会从这个日志记录支持中受益匪浅.
程序5-8实现了Employee类和Manager类的equals,hashCode和toString方法.
equals/EqualsTest.java如下所示:
package equals;
public class EqualsTest
{
public static void main(String[] args)
{
Employee alice1 = new Employee("Alice Adams", 70000, 1999, 12, 1);
Employee alice2 = alice1;
Employee alice3 = new Employee("Alice Adams", 70000, 1999, 12, 1);
Employee bob = new Employee("Bob", 500, 1999, 1, 2);
System.out.println("alice1 == alice2: " + (alice1 == alice2));
System.out.println("alice1 == alice3: " + (alice1 == alice3));
System.out.println("alice1.equals(alice3): " + alice1.equals(alice3));
System.out.println("alice1.equals(bob): " + alice1.equals(bob));
System.out.println("bob.toString(): " + bob);
Manager carl = new Manager("Carl Cracker", 80000, 1999, 12, 1);
Manager boss = new Manager("Carl Cracker", 80000, 1999, 12, 1);
boss.setBonus(5000);
System.out.println("boss.toString(): " + boss);
System.out.println("carl.equals(boss): " + carl.equals(boss));
System.out.println("alice1.hashCode(): " + alice1.hashCode());
System.out.println("alice3.hashCode(): " + alice3.hashCode());
System.out.println("bob.hashCode(): " + bob.hashCode());
System.out.println("carl.hashCode(): " + carl.hashCode());
}
}
equals/Employee.java如下所示:
package equals;
import java.util.Date;
import java.util.GregorianCalendar;
import java.util.Objects;
public class Employee
{
private String name;
private double salary;
private Date hireDay;
public Employee(String n, double s, int year, int month, int day)
{
name = n;
salary = s;
GregorianCalendar calendar = new GregorianCalendar(year, month - 1, day);
hireDay = calendar.getTime();
}
public String getName()
{
return name;
}
public double getSalary()
{
return salary;
}
public Date getHireDay()
{
return hireDay;
}
public void raiseSalary(double byPercent)
{
double raise = salary * byPercent / 100;
salary += raise;
}
public boolean equals(Object otherObject)
{
// a quick test to see if the objects are identical
if (this == otherObject)
return true;
// must return false if the explicit parameter is null
if (otherObject == null)
return false;
// if the classes don't match, they can't be equal
if (getClass() != otherObject.getClass())
return false;
// now we known otherObject is non-null Employee
Employee other = (Employee)otherObject;
// test whether the fields have identical values
return Objects.equals(name, other.name) && salary == other.salary && Objects.equals(hireDay, other.hireDay);
}
public int hashCode()
{
return Objects.hash(name, salary, hireDay);
}
public String toString()
{
return getClass().getName() + "[name = " + name + ", salary = " + salary + ", hireDay = " + hireDay + "]";
}
}
equals/Manager.java如下所示:
package equals;
public class Manager extends Employee
{
private double bonus;
public Manager(String n, double s, int year, int month, int day)
{
super(n, s, year, month, day);
bonus = 0;
}
public double getSalary()
{
double baseSalary = super.getSalary();
return baseSalary + bonus;
}
public void setBonus(double b)
{
bonus = b;
}
public boolean equals(Object otherObject)
{
if (!super.equals(otherObject))
return false;
Manager other = (Manager)otherObject;
// super.equals checked that this and other belong to the same class
return bonus == other.bonus;
}
public int hashCode()
{
return super.hashCode() + 17 * new Double(bonus).hashCode();
}
public String toString()
{
return super.toString() + "[bonus = " + bonus + "]";
}
}
运行结果如下所示:
java.lang.Object的方法如下所示:
Class getClass()
返回包含对象信息的类对象.
boolean equals(Object otherObject)
比较两个对象是否相等.如果两个对象指向同一块存储区域,方法返回 true;否则返回 false .
String toString()
返回描述该对象值的字符串.
String getName()
返回这个类的名字
Class getSuperclass()
以Class对象的形式返回这个类的超类信息