【Practical Java】實踐10:不要依賴equals()的預設實現

pengfoo發表於2012-08-29

實踐9 闡釋了何時使用==操作符以及何時使用equals()。如果你對後者的實現方式不聞不問,則在呼叫它時或許無法獲得你想要的結果。舉個例子,假設你正在為某個高爾夫器材批發店撰寫軟體,其中一個任務是計算庫存中的同類球數量。你可能已經為高爾夫球撰寫了如下的class:

package fp;

public class Golfball
{
	private String brand;//品牌
	private String make;//型號
	private int compression;//彈性
	
	public Golfball(String br, String mk, int comp)
	{
		brand = br;
		make = mk;
		compression = comp;
	}

	public String getBrand() 
	{
		return brand;
	}


	public String getMake() 
	{
		return make;
	}


	public int getCompression() 
	{
		return compression;
	}
	
	
}

每一個Golfball 物件都具有品牌(brand)、型號( make)和彈性( compression)三個屬性。兩個Golfball 物件相等意味所有三個屬性都必須相同。進一步假設,每一個Golfball 物件都被儲存於某資料庫中。日後當你訪問資料庫時,必須確定哪些Golfball 物件是同型別的,才能產生正確的計算。下面是一段用以比較Golfball 物件的程式碼:

package fp;

public class Test
{
	
	
	public static void main(String args[])
	{
		Golfball gb1 = new Golfball("BrandX","Professional",100);
		Golfball gb2 = new Golfball("BrandX","Professional",100);
		
		if(gb1.equals(gb2))
		{
			System.out.println("ball1 equals ball2");
		}
		else
		{
			System.out.println("ball1 does not equal ball2");
		}
	}
}

輸出如下:

ball1 does not equal ball2

本例中的Golfball 物件並沒有實現自己的equals(),因此呼叫的是java.lang.Object 的equals()。

 public boolean equals(Object obj) {
	return (this == obj);
    }

你想知道的其實是高爾夫球的品牌(brand)、型號(make)和彈性(compression)是否相同。與改善這個問題,Golfball class 必須實現自己的equals()函式,而不能寄望於java.lang.Object 提供的預設實現程式碼。

package fp;

public class Golfball
{
	//as before...	
	public boolean equals(Object obj)
	{
		if (this == obj)
			return true;
		
		if(obj != null && getClass() == obj.getClass())
		{
			Golfball gb = (Golfball)obj;
			if(brand.equals(gb.getBrand()) &&
					make.equals(gb.getMake()) &&
					compression == gb.getCompression())
				return true;
		}
		
		return false;
	}
	
	
}

到目前為止你的設計很出色,高爾夫器材批發店的程式運轉的也很好。可是有一天,某位程式設計師(在閱讀了《Practical Java》實踐31 之後)打算修改Golfball 物件的brand 和make 值域,以StringBuffer 替換String。現在,Golfball class 變成了這樣子:

public class Golfball
{
//	private String brand;//品牌
//	private String make;//型號
	private StringBuffer brand;//品牌
	private StringBuffer make;//型號
	private int compression;//彈性
	
	public Golfball(StringBuffer br, StringBuffer mk, int comp)
	{
		brand = br;
		make = mk;
		compression = comp;
	}

	public StringBuffer getBrand() 
	{
		return brand;
	}


	public StringBuffer getMake() 
	{
		return make;
	}

看起來這是對class 無傷大雅的修改。你並沒有改變equals(),只是改動Golfball class 的其他部分。Golfball class 僅僅從使用Strinng 轉為使用StringBuffer。但在完成了修改之後,你會驚訝的看到程式又不能正常運作了。它所產生的輸出如下:

ball1 does not equal ball2

Java 的String class 正確實現了一個equals(),但StringBuffer class 根本就沒有實現它。由於這個原因,你一定猜到了,你所呼叫的是java.lang.Object的equals()。這是因為StringBuffer 的父類是java.lang.Object,這就說得通了。我們先前已經討論過,java.lang.Object 的equals()在此情況下不能用。這也正是你先前自行撰寫equals()的原因。

這個問題有數個解決方案:
1. 回到使用string 物件的老路子。
2. 修改Golfball 的equals(),使之先將StringBuffer 物件轉換為String 物件,然後再呼叫equals()。

public boolean equals(Object obj)
	{
		if (this == obj)
			return true;
		
		if(obj != null && getClass() == obj.getClass())
		{
			Golfball gb = (Golfball)obj;
			if(brand.toString().equals(gb.getBrand().toString()) &&
					make.toString().equals(gb.getMake().toString()) &&
					compression == gb.getCompression())
				return true;
		}
		
		return false;
	}

3. 撰寫一個你自己的StringBuffer class,其中包含equals()。

package fp;

public class MyStringBuffer 
{
	private StringBuffer sb;
	
	public MyStringBuffer(String str)
	{
		sb = new StringBuffer(str);
	}
	
	public int length()
	{
		return sb.length();
	}
	
	public synchronized char charAt(int index)
	{
		return sb.charAt(index);
	}
	
	public boolean equals(Object obj)
	{
		if(this == obj)
			return true;
		
		if(obj != null && getClass() == obj.getClass())
		{
			MyStringBuffer msb = (MyStringBuffer)obj;
			int len = length();
			if(len != msb.length())
				return false;
			else
			{
				int index = 0;
				while(index < len)
				{
					if(charAt(index) != msb.charAt(index))
						return false;
					index++;
				}
				return true;
			}
		}
		else
			return false;
	}
}


4. 放棄equals(),撰寫你自己的compare()函式,用它來比較StringBuffer
物件的相等性。


 

相關文章