您的位置:首页 > 移动开发 > Objective-C

JAVA学习脚印7 : Object类的四个关键方法

2014-04-08 19:50 525 查看

JAVA学习脚印7 : Object类的四个关键方法

本节学习Object类的四个关键方法,包括equals方法、hashCode方法、toString方法、clone方法。

这四个方法将由大部分类包括自定义类继承或者覆盖,本节内容需要重点掌握

 

java中每个类都是由Object类派生而来的,如果没有明确指出某个类的超类,则该类的超类就是Object。

Object类有四个关键方法,需要我们注意。

1.equals方法(重难点)

Object类的equals方法,实现为:

public boolean equals(Object obj) {
return (this == obj);
}可以看出,Object类的equals方法用于判断两个对象是否具有相同的引用,我们可以称其为引用相等测试。

当两个对象具有相同的引用时,它们一定是相等的;但是在实际中,我们也需要作状态相等测试,即判断两个对象的状态是否相等,这在实际中具有应用意义。

例如,判断两个雇员对象,如果其Id完全一致则认为两个对象相等(Employee类,请参见例5-3)。

《java核心技术》中提供的,编写完美的equals方法的建议:

1)显式参数命名为otherObject,稍后需要将其转换为另一个叫做other的变量。

2)判断this与otherObject是否引用同一个对象。

3)判断otherObject是否为null,如果为null则返回false。

4)判断this与otherObject是否属于同一个类,如果equals的语义在每个子类中有所改变则使用getClass检测:

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

如果所有的子类拥有统一的语义,就使用instanceof检测,

if(!(otherObjct instanceof ClassName)) return false;

例如java.util.Date类的equals方法如下:

public boolean equals(Object obj) {
return obj instanceof Date && getTime() == ((Date) obj).getTime();
}
5)将otherObject转换为相应的类类型变量。

6)进行状态相等测试,即进行类的所有域的相等测试。

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

例5-3示例了equals方法的书写。

2)hashCode方法

 

hashCode方法将产生一个由对象导出的整型值。注意,equals和hashCode方法定义必须一致,即如果x.equals(y)返回true,那么x.hashCode就必须与y.hashCode具有相同的值。

例如java.util.Date类的equals方法比较的是Date对象以毫秒表示的长整数,那么hashCode方法中也是对这个数值进行相应计算来产生散列码的。

<span style="font-size:14px;">public boolean equals(Object obj) {
return obj instanceof Date && getTime() == ((Date) obj).getTime();
}
public int hashCode() {
long ht = this.getTime();
return (int) ht ^ (int) (ht >> 32);
}</span>


例5-3示例了hashCode方法的书写。

3)toString方法

 

toString方法用于返回表示对象值的字符串。默认的toString方法为:

<span style="font-size:14px;"> public String toString() {
return getClass().getinName() + "@" + Integer.toHexString(hashCode());
}</span>

因此调用 System.out.println(staffs[2].toString());

将输出:  com.learningjava.Manager@13e5454

建议为自己定义的每个类实现具有意义的toString方法,例5-3示例了toString方法的书写。

例5-3EqualsTest.java
package com.learningjava;

import java.util.Date;
import java.util.GregorianCalendar;
/**
* this program demonstrate using equals
* from the book 《Core Java,Volume I:Fundamentals》
*/
public class EqualsTest {
public static void main(String[] args) {

Employee alice1 = new Employee("Alice Adams",7500,1987,12,15);
Employee alice2 = alice1;
Employee alice3 = new Employee("Alice Adams",7500,1987,12,15);
Employee bob = new Employee("Bob Brandson",5000,1989,10,1);

//true    refer to the same object
System.out.println("alice1 == alice2: "+(alice1 == alice2));

//false   refer to different object
System.out.println("alice1 == alice3: "+(alice1 == alice3));

//true    refer to different objcet that has identical fileds
System.out.println("alice1.equals(alice3): "+alice1.equals(alice3));

//false   refer to different object that has different fileds
System.out.println("alice1.equals(bob): "+alice1.equals(bob));

System.out.println("bob.toString(): "+bob);

Manager carl = new Manager("Carl Cracker",8000,1987,12,15);
Manager boss = new Manager("Carl Cracker",8000,1987,12,15);
boss.setBonus(5000);

System.out.println("boss.toString(): "+boss);

//false    boss have much bonus than carl
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());
}
}

/**
* a class to descript employee
* from the book 《Core Java,Volume I:Fundamentals》
*/
class Employee {

/* (non-Javadoc)
* @see java.lang.Object#hashCode()
*/
@Override
public int hashCode() {
// TODO Auto-generated method stub
return 7*name.hashCode()+11*new Double(salary).hashCode()+13*hireDay.hashCode();
}
/* (non-Javadoc)
* @see java.lang.Object#toString()
*/
@Override
public String toString() {
// TODO Auto-generated method stub
return getClass().getName()+"[name="+name+",salary="+getSalary()+",hireDay="+hireDay+"]";
}
/**
* @param name name to set
* @param salary salary to set
* @param hireday hireday to set
*/
public Employee(String name, double salary, Date hireday) {
this.name = name;
this.salary = salary;
this.hireDay = hireday;
setId();
}
/**
*
* @param name name to set
* @param salary salary to set
* @param year month day  to create a GregorianCalendar
*/
public Employee(String name, double salary, int year,int month,int day) {
this.name = name;
this.salary = salary;
GregorianCalendar calendar = new GregorianCalendar(year,month-1,day);
this.hireDay = calendar.getTime();
setId();
}
/* (non-Javadoc)
* @see java.lang.Object#equals(java.lang.Object)
*/
@Override
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 know otherObject is a non-null Employee
Employee other = (Employee)otherObject;

//test whether the fields have identical values
return name.equals(other.name) && salary == other.salary
&& hireDay.equals(other.hireDay);

}
public void raiseSalary(double percent) {
double raise = salary*percent/100;
salary += raise;
}
public String getName() {
return name;
}
public double getSalary() {
return salary;
}
public void setSalary(double salary) {
this.salary = salary;
}
public Date getHireday() {
return hireDay;
}
public void setHireday(Date hireday) {
this.hireDay = hireday;
}
public int getId() {
return id;
}
private void setId() {
this.id = nextId;
nextId++;
}
private String name;
private double salary;
private Date hireDay;
private int id;
private static int nextId = 1;
}
/**
* a class to descript manager
* from the book 《Core Java,Volume I:Fundamentals》
*/
class Manager extends Employee {

/* (non-Javadoc)
* @see com.learningjava.Employee#hashCode()
*/
@Override
public int hashCode() {
return super.hashCode()+17*new Double(bonus).hashCode();
}
/* (non-Javadoc)
* @see com.learningjava.Employee#toString()
*/
@Override
public String toString() {
return super.toString()+"[bonus="+bonus+"]";
}
/* (non-Javadoc)
* @see com.learningjava.Employee#equals(java.lang.Object)
*/
@Override
public boolean equals(Object otherObject) {

//call superclass euqal method to quick test
//if this and otherObject belong to the same class
//or the fields defined in superclass euqals
if(!super.equals(otherObject)) return false;

//compare the fields defined in the subclass
Manager other = (Manager)otherObject;
return bonus == other.bonus;

}
public void setBonus(double b) {
bonus = b;
}
public Manager(String name, double salary, int year,int month,int day) {
super(name, salary,year,month,day);
bonus = 0;
}
public Manager(String name, double salary, Date hireday) {
super(name, salary, hireday);
bonus = 0;
}
@Override
public double getSalary() {
double baseSalary = super.getSalary();
return baseSalary+bonus;
}

private double bonus;
}

4)clone方法——浅拷贝与深拷贝(重点)

 

克隆一个对象时,有三种方式:

1)直接拷贝一个变量

直接拷贝一个变量,原始变量与拷贝变量引用的是同一个对象,改变其中一个变量引用的对象将会对另一个变量产生影响。

例如没有定义Clone方法的时候,如下代码:

//directly copy a reference variable
Employee original1 = new Employee("Jack",5000,1980,11,5);
Employee copy1 = original1;
copy1.raiseSalary(10);
System.out.println("copy1.getSalary(): "+copy1.getSalary());  //print 5500.0
System.out.println("original1.getSalary(): "+original1.getSalary());//print 5500.0

可以看到, copy1执行操作将影响original1所引用所的对象。

2)利用Object类的默认拷贝方法

覆盖了Object类的clone方法,但是使用其默认的拷贝功能.这里Cloneable是一个标记接口,表明需要进行克隆处理。如下代码:

//using the default clone method inherit from Objcet
Employee original2 = new Employee("Jack",5000,1980,11,5);
try {
Employee copy2 = original2.clone();

copy2.raiseSalary(10);
System.out.println("copy2.getSalary(): "+copy2.getSalary());//print 5500.0
System.out.println("original2.getSalary(): "+original2.getSalary());//print  5000.0

copy2.setHireday(1990,10,1);
System.out.println("copy2.getHireday(): "+copy2.getHireday());//print 1990
System.out.println("original2.getHireday(): "+original2.getHireday());//print 1990

} catch (CloneNotSupportedException e) {
e.printStackTrace();
}

class Employee implements Cloneable{

public Employee clone() throws CloneNotSupportedException {
//use default Object.clone
return (Employee)super.clone();
}
public void setHireday(int year,int month,int day) {
Date newHireDay = new GregorianCalendar(year,month-1,day).getTime();
this.hireDay.setTime(newHireDay.getTime());
}
...
}


这样实现这样的这样拷贝仍然存在问题,copy2.setHireday(1990,10,1);影响到了 original2的hireDay对象状态。这种Object类的默认拷贝属于浅拷贝,它只能将各个域进行对应的拷贝。如果对象中的域都是基本类型的变量则这种拷贝就不会出错,但是对于包含子对象引用的域,拷贝的结果是使得这两个域引用同一个子对象,即原始对象与克隆对象共享这个子对象,因此copy2.setHireday(1990,10,1);影响到了 original2的hireDay,而copy2.raiseSalary(10)没有影响到original2的salary。

 

3)重新定义拷贝方法

浅拷贝对于只有基本数据类域或者子对象不可变的域的类来说将不会产生问题,但是对于可变子对象的拷贝就会产生问题就像hireDay这种可变的子对象就出现了问题,因此需要使用深拷贝。

深拷贝的clone方法,首先使用其默认的拷贝功能,然后通过调用可变子对象的clone方法进行修补,如下代码:

//using the override clone method
Employee original3 = new Employee("Jack",5000,1980,11,5);
try {
Employee copy3 = original3.clone();

copy3.raiseSalary(10);
System.out.println("copy3.getSalary(): "+copy3.getSalary());//print 5500.0
System.out.println("original3.getSalary(): "+original3.getSalary());//print  5000.0

copy3.setHireday(1990,10,1);
System.out.println("copy3.getHireday(): "+copy3.getHireday());//print 1990
System.out.println("original3.getHireday(): "+original3.getHireday());//print 1980

} catch (CloneNotSupportedException e) {
e.printStackTrace();
}
}
class Employee implements Cloneable{

public Employee clone() throws CloneNotSupportedException {

//call Object.clone
Employee cloned = (Employee)super.clone();

//clone nutable fields
cloned.hireDay = (Date)hireDay.clone();

return cloned;
}
….
}

总之,定义一个类的克隆方法需要考虑:

1.是否应该使用克隆机制。

2.默认的克隆方法是否够用。

3.默认的克隆方法是否可以通过调用可变子对象的clone方法得到修补。

注意,要使用克隆机制,类必须实现Cloneable接口,并且使用public访问修饰符重新定义clone方法。

由以上可见,如果编写方法返回一个可变对象的引用,那么首先应该对它进行克隆,返回该对象的一个副本,否则返回原对象的引用后在类外将可能发生修改操作,导致数据不一致。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: