你所忽略的,覆盖equals时需要注意的事项《effective java》
我們都知道Object的equals的比較其實就是==的比較,其實是內存中的存放地址的比較。正常邏輯上:類的每個實例本質上都是唯一的。
在工作中我們實際的業務邏輯往往有可能出現一些相對特殊的需求需要對equals方法進行重寫,那么重寫equals需要注意哪些規則或者通用的約定呢?
?
equals方法實現了等價關系(equivalence relation):
- 自反性(reflexive)。對于任何非null的引用值x,x.equals(x)必須返回true。
- 對稱性(symmetric)。對于任何非null的引用值x和y,當且僅當y.equals(x)返回true時,x.equals(y)必須返回true。
- 傳遞性(transitive)。對于任何非null的引用值x、y和z。如果x.equals(y)返回true,并且y.equals(z)也返回true,那么x.equals(z)也必須返回true。
- 一致性(consistent)。對于任何非null的引用值x和y,只要equals的比較操作在對象中所用的信息沒有被修改,多次調用x.equals(x)就會一致地返回true,或者一致的返回false。
- 對于任何非null的引用值x,x.equals(null)必須返回false。
第一點自反性不需要多說,基本上不可能會出現違背這條約定的情況當自己和自己比較的時候返回false。
第二點對稱性,這種情況還是有可能會出現的。我們可以假設一個場景,這個場景是我們創建一個類并且里面只有一個String屬性字段,這個類需要實現的是可以不區分字符串的大小寫。
pubilc final class IgnoreCaseString {private final String s;public IgnoreCaseString(String s) {if (s == null)throw new NullPointerException();this.s = s;}@Overridepublic boolean equals(Object o) {if (o instanceof IgnoreCaseString)return s.equalsIgnoreCase(((IgnoreCaseString) o).s);if (o instanceof String)return s.equalsIgnoreCase((String) o);return false;}...//更多代碼(重寫equals就需要重寫hashCode) }
在這個類中,equals方法的意圖非常好,它的企圖是可以與普通的字符串對象進行互操作。但是這段代碼無意中觸犯了對稱性這個約定,從new?IgnoreCaseString(“Po”).equals("po")是為true的,但是反過來“po”.equals(new IgnoreCaseString("Po"))的結果是false。假如違反了這一情況而沒有去更正,他會破壞已有的集合框架的一些方法,使其變的不在準確。
IgnoreCaseString ics = new IgnoreCaseString("Po");List<IgnoreCaseString> list = new ArrayList<IgnoreCaseString>();list.add(ics);System.out.println(list.contains("po"));List<String> list1 = new ArrayList<>();list1.add("po");System.out.println(list1.contains(ics));結果是,此時list.contains(s)會返回什么結果呢?沒人知道,在Sun的當前實現中,它碰巧返回false,但這只是這個特定實現得出的結果而已。在其他的實現中,它有可能返回true(如上面代碼中的list1.contains(ics)),或者拋出一個運行時(runtime)異常。一旦違反了equals約定,當其他對象面對你的對象時,你完全不知道這些對象的行為會這么樣。
第三點傳遞性,equals約定:如果一個對象等于第二個對象,并且第二個對象又等于第三個對象,則第一個對象一定等于第三個對象。無意識違反這一情況其實不難想象,考慮子類的情形,子類增加的信息會影響到equals的比較結果。
public class TwoDCoordinate {private final int x;private final int y;public TwoDCoordinate(int x, int y) {this.x = x;this.y = y;}@Overridepublic boolean equals(Object o) {if (!(o instanceof TwoDCoordinate))return false;TwoDCoordinate p = (TwoDCoordinate)o;return p.x == x && p.y == y;}...//更多代碼(重寫equals就需要重寫hashCode) }
public class ThreeDCoordinate extends TwoDCoordinate{
private final int z;
public ThreeDCoordinate(int x, int y, int z) {
super(x, y); this.z=z;
}
@Override
public boolean equals(Object o) {
if (!(o instanceof TwoDCoordinate))
return false;
if (!(o instanceof ThreeDCoordinate))
return o.equals(this);
return super.equals(o) && this.z == ((ThreeDCoordinate) o).z;
}
...//更多代碼(重寫equals就需要重寫hashCode) } ThreeDCoordinate t1 = new ThreeDCoordinate(1, 2, 3);TwoDCoordinate t2 = new TwoDCoordinate(1, 2);ThreeDCoordinate t3 = new ThreeDCoordinate(1, 2, 4);System.out.println(t1.equals(t2));System.out.println(t2.equals(t1));System.out.println(t2.equals(t3));System.out.println(t1.equals(t3));
結果是true??true? ?true? ?false,上述代碼已經滿足了自反性和對稱性的約定,但是沒有滿足傳遞性,t1.equals(t2)為true,t2.equals(t3)為true,t1.equals(t3)卻為false。
這種情況很多程序員會犯。
?想要避免這種情況,其實可以用getClass測試代替instanceof測試。如將TwoDCoordinate 的equals 方法改為
public boolean equals(Object o) {if (o == null || o.getClass() != this.getClass())return false;TwoDCoordinate p = (TwoDCoordinate)o;return p.x == x && p.y == y;}這種替代方式其實不會太糟糕,但是結果卻不會太理想,暫時沒有想到令人滿意的辦法實現既可以擴展又不可實例化的類,但是可以考慮復合優先于繼承。
第四點一致性,equals約定的第四個要求是,如果兩個對象相等,它們就必須始終保持相等,除非它們中有一個對象(或者兩個都)被修改了。換句話說,可變對象在不同的時候可以與不同的對象相等,而不可變對象則不會這樣。當你在寫一個類的時候,應該仔細考慮她是否應該是不可變的。如果認為它應該是不可變的,就必須保證equals方法滿足這樣的限制條件:相等的對象永遠相等,不相等的對象永遠不相等。
?
轉載于:https://www.cnblogs.com/saoyou/p/10318517.html
創作挑戰賽新人創作獎勵來咯,堅持創作打卡瓜分現金大獎總結
以上是生活随笔為你收集整理的你所忽略的,覆盖equals时需要注意的事项《effective java》的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 【工具】js脚本下载百度文库生成word
- 下一篇: 编译器的2点优化