您好,登錄后才能下訂單哦!
重寫java object類的equals方法
覆蓋equals請(qǐng)遵守通用約定
似乎覆蓋equals方法看起來(lái)似乎是一件平常甚至極其簡(jiǎn)單的事情,
但是有許多覆蓋方式會(huì)導(dǎo)致錯(cuò)誤,并且會(huì)表現(xiàn)出超出預(yù)期的行為,
而有可能數(shù)小時(shí)也無(wú)法找到錯(cuò)誤的位置。(比如說(shuō)把參數(shù)改成了非Object類型)
1. 類的每一個(gè)實(shí)例在本質(zhì)上都是唯一的
( 從內(nèi)存的角度來(lái)講是這樣的),對(duì)于代表活動(dòng)而不是值(value)的類來(lái)說(shuō)更是如此,
例如Thread。
Object提供equals的實(shí)現(xiàn)對(duì)于這些類來(lái)說(shuō)是正確的行為
2. 類沒(méi)有必要提供“邏輯相等”的測(cè)試功能
3.超類已經(jīng)覆蓋了equals方法,超類的行為對(duì)于子類來(lái)說(shuō)同樣也是合適的
4.類是私有的或者是包級(jí)私有的,可以確定它的equals方法永遠(yuǎn)不會(huì)被外界調(diào)用
如果非常想規(guī)避風(fēng)險(xiǎn),可以覆蓋equals方法,
來(lái)確保來(lái)自O(shè)bject或者超類的方法永遠(yuǎn)不會(huì)被意外調(diào)用。
那么什么時(shí)候應(yīng)該覆蓋equals方法
如果類具有自己特有的“邏輯相等”概念(不同于對(duì)象等同的概念)
而且超類沒(méi)有覆蓋equals方法。這通常屬于"值類"(value class)的情形
例如 一個(gè)圓 Circle類,內(nèi)有一個(gè)私有的成員變量radius半徑
可以認(rèn)為,radius相等代表了兩個(gè)實(shí)例在邏輯上相等(或許可以再加上坐標(biāo))
再看String類,程序員在利用equals方法比較值對(duì)象的引用時(shí),
更希望知道它們邏輯上是否相等,而不希望知道它們到底是不是同一個(gè)對(duì)象
為滿足要求,不僅必須覆蓋equals方法,
而且這樣做也使得這個(gè)類的實(shí)例
可以被用作映射表 (map) 的鍵 (key) ,或者集合set的元素,
使其表現(xiàn)出符合預(yù)期的行為
注意:有一種“值類”不需要覆蓋equals方法
即實(shí)例受控,甚至于單例模式,
確保每個(gè)實(shí)例的“值”至多只存在一個(gè)對(duì)象,甚至僅能存在一個(gè)實(shí)例
(好像太嚴(yán)格了,不過(guò)只能存在一個(gè)對(duì)象有什么可比的呢,就像客戶端只能有一個(gè)連接服務(wù)器的socket類實(shí)例一樣)
覆蓋equals時(shí)請(qǐng)遵守通用約定
自反性,對(duì)稱性以及傳遞性是最基礎(chǔ)的約定
x.equals(x) = x.equals(x) (好像很傻) x.equals(y) = y.equals(x)(這也是最容易出現(xiàn)問(wèn)題的地方) x.equals(y) = y.equals(z) 那么x.equals(z) == true
一致性:
對(duì)于任何非null引用值x和y,只要equals方法的比較操作在對(duì)象中引用的信息沒(méi)有被修該,多次調(diào)用x.equals(y)返回的結(jié)果一致
對(duì)于任何非null的引用值x,x.equals(null)必須返回false
下面是一個(gè)不區(qū)分大小寫字符串類的定義(注意 是反例!!違反了對(duì)稱性)
public class CaseInsensitiveString { private final String s;//存有不可變字符串s public CaseInsensitiveString(String s) { this.s = s; } @Override //重寫equals方法,請(qǐng)注意參數(shù)為Object public boolean equals(Object o) { if (o instanceof CaseInsensitiveString)//判斷傳入的是同類型的參數(shù) { return s.equalsIgnoreCase(((CaseInsensitiveString) o).s); } if (o instanceof String) { return s.equalsIgnoreCase(((String) o)); } return false; } }
注意:equals方法中的參數(shù)Object o ,一定不要定義成其他類型??!
在絕大多數(shù)情況都要加上一句instanceof 來(lái)給o 賦予類型?。?!
這個(gè)類的equals方法看起來(lái)設(shè)計(jì)的很有想法,不僅和自身比較,也希望和String類型進(jìn)行比較。
//也確實(shí)看起來(lái)實(shí)現(xiàn)了功能 public static void main(String[] args) { //不區(qū)分大小寫的字符串Point CaseInsensitiveString cis = new CaseInsensitiveString("Point"); String s = "point"; System.out.printf(cis.equals(s)+"");//true }
但是 s.equals(cis)卻返回了false
//s.equals(cis)卻返回了false public static void main(String[] args) { CaseInsensitiveString cis = new CaseInsensitiveString("Point"); String s = "point"; System.out.printf(cis.equals(s)+"");//true System.out.printf(s.equals(cis)+"");//false }
雖然CaseInsensitiveString類中的equals方法知道普通的字符串對(duì)象
但是String類中的equals方法并不知道不區(qū)分大小寫的字符串,
導(dǎo)致了超出預(yù)期的錯(cuò)誤
顯然違反了對(duì)稱性。
一旦違反了equals約定,當(dāng)其他對(duì)象面對(duì)你的對(duì)象是,你完全不知道這些對(duì)象的行為會(huì)怎樣
為了解決這個(gè)問(wèn)題,只需要把企圖和String互相操作的代碼刪除就可以了
public class CaseInsensitiveString { private final String s;//存有不可變字符串s public CaseInsensitiveString(String s) { this.s = s; } @Override //重寫equals方法,請(qǐng)注意參數(shù)為Object public boolean equals(Object o) { if (o instanceof CaseInsensitiveString)//判斷傳入的是同類型的參數(shù) { return s.equalsIgnoreCase(((CaseInsensitiveString) o).s); } /*刪除掉的與String互操作的代碼*/ return false; } }
考慮這樣一種情況
有一個(gè)坐標(biāo)類,內(nèi)有成員變量x和y
并提供equals方法
public class Point { private final int x; private final int y; public Point(int x,int y) { this.x=x; this.y=y; } @Override public boolean equals(Object o ) { if(o instanceof Point) { Point p = (Point)o; return (this.x == p.x && this.y==p.y); } return false; } }
好像簡(jiǎn)單到不能再簡(jiǎn)單的定義
那么擴(kuò)展(繼承)一下,為這個(gè)點(diǎn)添加顏色信息
public class ColorPoint extends Point{ private final Color color; public ColorPoint(int x,int y,Color color) { super(x,y); this.color=color; } }
現(xiàn)在考慮一下
equals方法是什么樣的呢?
如果子類ColoePoint完全不提供equals方法,而是直接使用父類的equals方法
在equals做比較的時(shí)候顏色信息就被忽略掉了。
雖然這么做不會(huì)違反equals約定
但顯然是無(wú)法接受的。
那么就重寫一個(gè)equals方法。只有當(dāng)坐標(biāo)點(diǎn)x,y相同且顏色也相同時(shí)
equals才返回true
public class ColorPoint extends Point{ private final Color color; public ColorPoint(int x,int y,Color color) { super(x,y); this.color=color; } @Override public boolean equals(Object o) { if(o instanceof ColorPoint) { return (super.equals(o)&& this.color== ( (ColorPoint) o ).color);//使用父類的equals方法 } return false; } }
這個(gè)方法的問(wèn)題在于,比較普通點(diǎn)(沒(méi)有顏色的Point實(shí)例)和
有色點(diǎn)(ColorPoint實(shí)例)比較,以及相反的情況時(shí)可能會(huì)得到不同的結(jié)果
前一種忽略的顏色信息,而后一種總時(shí)返回false(因?yàn)閰?shù)類型不正確)
public static void main(String[] args) { Point p = new Point(1,2); ColorPoint cp = new ColorPoint(1,2,Color.RED); System.out.printf(p.equals(cp)+"");//true System.out.printf(cp.equals(p)+"");//false }
可以嘗試以ColorPoint.equals在進(jìn)行“混合顏色比較時(shí)”忽略顏色信息
但這樣做確實(shí)提供了對(duì)稱性,但卻犧牲了傳遞性。
那該怎么解決???
事實(shí)上,這是面向?qū)ο笳Z(yǔ)言中關(guān)于等價(jià)關(guān)系的一個(gè)基本問(wèn)題。
我們無(wú)法在擴(kuò)展可實(shí)例化的類的同時(shí),既增加新的組件,同時(shí)又保留equals約定
雖然沒(méi)有一種令人滿意的辦法既可以擴(kuò)展可實(shí)例化的類,又增加組件,但還有一種不錯(cuò)的方法。復(fù)合優(yōu)先于繼承
我們不再讓ColorPoint繼承Point,
而是在ColorPoint類中添加一個(gè)私有的Point域
以及一個(gè)公有的視圖(view)方法
此(view)方法返回一個(gè)與該色點(diǎn)處在相同位置的普通Point對(duì)象。
public class ColorPoint{ private final Color color; private final Point point; public ColorPoint(int x,int y,Color color) { point = new Point(x,y); this.color=color; } /*view*/ public Point asPoint() { return point; } @Override public boolean equals(Object o) { if(o instanceof ColorPoint) { ColorPoint cp =(ColorPoint)o; return this.point.equals(cp.point)&& this.color.equals(cp.color);//調(diào)用Point以及Color類的equals方法 } //那么有色點(diǎn)與無(wú)色點(diǎn)會(huì)被判斷為不相同而返回false return false; } }
以上就是本文的全部?jī)?nèi)容,希望對(duì)大家的學(xué)習(xí)有所幫助,也希望大家多多支持億速云。
免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場(chǎng),如果涉及侵權(quán)請(qǐng)聯(lián)系站長(zhǎng)郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。