JAVA学習足跡7:Object類の四つの肝心な方法

12119 ワード

JAVA学習足跡7:Object類の四つの肝心な方法
このセクションでは、equalsメソッド、hashCodeメソッド、toStringメソッド、cloneメソッドなど、Objectクラスの4つの重要なメソッドを学習します.
この4つの方法は、ほとんどのクラスがカスタムクラスの継承または上書きを含むため、このセクションでは重点的に把握する必要があります.
 
JAvaの各クラスはObjectクラスから派生しており,あるクラスのスーパークラスが明確に指摘されていない場合,そのクラスのスーパークラスはObjectである.
Objectクラスには4つの重要な方法があり、注意が必要です.
1.equalsメソッド(難点)
Objectクラスのequalsメソッドは、次のように実装されます.
public boolean equals(Object obj) {
        return (this == obj);
    }

Objectクラスのequalsメソッドは、2つのオブジェクトが同じ参照を持っているかどうかを判断するために使用され、参照等しいテストと呼ぶことができます.
2つのオブジェクトが同じ参照を持っている場合、それらは必ず等しいです.しかし,実際には,2つのオブジェクトの状態が等しいか否かを判断する状態等しいテストも必要であり,これは実際に応用意義がある.
たとえば、2つの従業員オブジェクトを判断し、Idが完全に一致している場合、2つのオブジェクトが等しいと判断します(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-3 EqualsTest.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つのオブジェクトをクローンするには、次の3つの方法があります.
1)変数を直接コピーする
1つの変数を直接コピーします.元の変数はコピー変数と同じオブジェクトを参照します.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

見えますが、 copy 1の実行はoriginal 1が参照するオブジェクトに影響します.
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());
	}
   ...
}

このようなコピーの実現には依然として問題があり、copy 2.setHireday(1990,10,1);影響を及ぼした original 2のhireDayオブジェクト状態.このようなObjectクラスのデフォルトコピーは浅いコピーに属し、各ドメインを対応するコピーしかできません.オブジェクト内のドメインがすべて基本タイプの変数である場合、このコピーはエラーが発生しませんが、サブオブジェクト参照を含むドメインでは、コピーの結果、元のオブジェクトがクローンオブジェクトと共有される同じサブオブジェクトを参照することになります.したがって、copy 2.setHireday(1990,10,1);影響を及ぼした original 2のhireDay,copy 2.raiseSalary(10)はoriginal 2の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メソッドを再定義する必要があります.
以上から分かるように、作成方法が可変オブジェクトの参照を返す場合は、まずクローン化し、そのオブジェクトのコピーを返す必要があります.そうしないと、元のオブジェクトの参照を返すと、クラス外で変更操作が発生し、データが一致しない可能性があります.