繼承關係下的equals改寫

來源:互聯網
上載者:User



本作品採用知識共用署名-非商業性使用-相同方式共用 2.5 中國大陸許可協議
進行許可。

 

 

學習過Java的人都知道,Java對象的內容比較依靠的是Object類的equals方法。改寫這個方法有嚴格的要求,JDK API中是這樣描述的:

public boolean equals(Object obj);

指示其他某個對象是否與此對象“相等”。

equals 方法在非Null 物件引用上實現相等關係:

  • 自反性:對於任何非Null 參考值 x,x.equals(x) 都應返回 true。
  • 對稱性:對於任何非Null 參考值 x 和 y,若且唯若 y.equals(x) 返回 true 時,x.equals(y) 也一定返回 true。
  • 傳遞性:對於任何非Null 參考值 x、y 和 z,如果 x.equals(y) 返回 true,並且 y.equals(z) 返回 true,那麼 x.equals(z) 也一定返回 true。
  • 一致性:對於任何非Null 參考值 x 和 y,多次調用 x.equals(y) 始終返回 true 或始終返回 false,前提是對象上 equals 比較中所用的資訊沒有被修改。
  • 對於任何非Null 參考值 x,x.equals(null) 一定返回 false。

注意:當此方法被重寫時,通常有必要重寫 hashCode 方法,以維護 hashCode 方法的常規協定,該協定聲明相等對象必須具有相等的雜湊碼。

Josh Bloch在Effective Java一書中介紹了一種通用的改寫equals的方法,Apache Commons Lang庫也為我們提供了兩個工具類來簡化改寫equals與hashCode的過程。關於這一點請參考利用
Commons Lang庫改寫equals與hashCode方法
一文。

具有繼承關係的對象比較

五大特性中的對稱性與傳遞性是其中比較麻煩的兩個,特別是當我們要進行子類對象與父類對象的內容比較時。如下面的例子:
父類代碼(Point類)
import org.apache.commons.lang.builder.EqualsBuilder;<br />import org.apache.commons.lang.builder.HashCodeBuilder;<br />public class Point {<br /> private final int x;<br /> private final int y;<br /> public Point(int x, int y) {<br /> this.x = x;<br /> this.y = y;<br /> }<br /> public int getX() {<br /> return x;<br /> }<br /> public int getY() {<br /> return y;<br /> }<br /> @Override<br /> public boolean equals(Object other) {<br /> if (other == this) {<br /> return true;<br /> }<br /> if (!(other instanceof Point)) {<br /> return false;<br /> }</p><p> Point that = (Point) other;<br /> return new EqualsBuilder()<br /> .append(this.getX(), that.getX())<br /> .append(this.getY(), that.getY())<br /> .isEquals();<br /> }<br /> @Override<br /> public int hashCode() {<br /> return new HashCodeBuilder(17, 37)<br /> .append(this.getX())<br /> .append(this.getY())<br /> .toHashCode();<br /> }<br />}

子類代碼(ColorPoint類)
import org.apache.commons.lang.builder.EqualsBuilder;<br />import org.apache.commons.lang.builder.HashCodeBuilder;<br />public class ColorPoint extends Point {<br /> private final Color color;<br /> public ColorPoint(int x, int y, Color color) {<br /> super(x, y);<br /> this.color = color;<br /> }<br /> public Color getColor() {<br /> return color;<br /> }<br /> @Override<br /> public boolean equals(Object other) {<br /> if (other == this) {<br /> return true;<br /> }<br /> if (!(other instanceof ColorPoint)) {<br /> return false;<br /> }<br /> ColorPoint that = (ColorPoint) other;<br /> return new EqualsBuilder()<br /> .appendSuper(super.equals(that))<br /> .append(this.getColor(), that.getColor())<br /> .isEquals();<br /> }<br />}

枚舉型Color
public enum Color {<br /> RED, ORANGE, YELLOW, GREEN, BLUE, INDIGO, VIOLET;<br />}

當你想要對Point與ColoredPoint兩種對象進行內容比較時,上述代碼將會違反對稱性原則。測試代碼如下:
public static void main(String[] args) {<br /> Point p1 = new Point(1, 2);<br /> ColorPoint p2 = new ColorPoint(1, 2, Color.RED);<br /> System.out.println(p1.equals(p2)); // prints true<br /> System.out.println(p2.equals(p1)); // prints false<br />}

那麼,如何才能正確比較兩個具有繼承關係的對象呢?

Josh Bloch的解決方案

 

Josh Bloch在Effective Java一書中提供的解決方案是:複合優先於繼承。也就是避開了繼承問題,使ColorPoint與Point之間沒有繼承關係,進而完全不可比。ColorPoint類的改造如下:
import org.apache.commons.lang.builder.EqualsBuilder;<br />import org.apache.commons.lang.builder.HashCodeBuilder;<br />public class ColorPoint{<br /> private final Point point;<br /> private final Color color;<br /> public ColorPoint(int x, int y, Color color) {<br /> this.point = new Point(x, y);<br /> this.color = color;<br /> }<br /> public Point getPoint() {<br /> return point;<br /> }</p><p> public Color getColor() {<br /> return color;<br /> }<br /> @Override<br /> public boolean equals(Object other) {<br /> if (other == this) {<br /> return true;<br /> }<br /> if (!(other instanceof ColorPoint)) {<br /> return false;<br /> }<br /> ColorPoint that = (ColorPoint) other;<br /> return new EqualsBuilder()<br /> .append(this.getPoint(), that.getPoint())<br /> .append(this.getColor(), that.getColor())<br /> .isEquals();<br /> }<br /> @Override<br /> public int hashCode() {<br /> return new HashCodeBuilder(17, 37)<br /> .append(this.getPoint())<br /> .append(this.getColor())<br /> .toHashCode();<br /> }<br />}

兩種對象的比較結果均為false,測試代碼如下:
public static void main(String[] args) {<br /> Point p1 = new Point(1, 2);<br /> ColorPoint p2 = new ColorPoint(1, 2, Color.RED);<br /> System.out.println(p1.equals(p2)); // prints false<br /> System.out.println(p2.equals(p1)); // prints false<br />}

Josh Bloch為我們提供了避免錯誤的方法,但沒有解決我們提出的問題!

Martin Odersky的解決方案

與Josh Bloch的解決方案相對應,Martin Odersky、Lex Spoon與Bill Venners三人在合作完成的How to Write an Equality Method in Java
一文中提出了另一個解決方案:canEqual 方法。此文的中文版可查看酷殼翻譯整理的如何在Java中避免equals方法的隱藏陷阱
。本質上這個方案提供一個判斷函數,判斷哪些對象可以與本類對象進行比較。
據此,Point類的實現如下:
import org.apache.commons.lang.builder.EqualsBuilder;<br />import org.apache.commons.lang.builder.HashCodeBuilder;<br />public class Point {<br /> private final int x;<br /> private final int y;<br /> public Point(int x, int y) {<br /> this.x = x;<br /> this.y = y;<br /> }<br /> public int getX() {<br /> return x;<br /> }<br /> public int getY() {<br /> return y;<br /> }<br /> @Override<br /> public boolean equals(Object other) {<br /> if (other == this) {<br /> return true;<br /> }</p><p> // 此步驟判斷“我”是否可以比較“他”<br /> if (!this.canEqual(other)) {<br /> return false;<br /> }</p><p> Point that = (Point) other;<br /> // 此步驟判斷“他”是否可以比較“我”<br /> if (!that.canEqual(this)) {<br /> return false;<br /> }</p><p> return new EqualsBuilder()<br /> .append(this.getX(), that.getX())<br /> .append(this.getY(), that.getY())<br /> .isEquals();<br /> }<br /> @Override<br /> public int hashCode() {<br /> return new HashCodeBuilder(17, 37)<br /> .append(this.getX())<br /> .append(this.getY())<br /> .toHashCode();<br /> }<br /> public boolean canEqual(Object other) {<br /> // Point對象可以與所有繼承自Point的對象進行比較<br /> return (other instanceof Point);<br /> }<br />}

ColorPoint類的實現如下:
import org.apache.commons.lang.builder.EqualsBuilder;<br />import org.apache.commons.lang.builder.HashCodeBuilder;<br />public class ColorPoint extends Point {<br /> private final Color color;<br /> public ColorPoint(int x, int y, Color color) {<br /> super(x, y);<br /> this.color = color;<br /> }<br /> public Color getColor() {<br /> return color;<br /> }<br /> @Override<br /> public boolean equals(Object other) {<br /> if (other == this) {<br /> return true;<br /> }<br /> // 此步驟判斷“我”是否可以比較“他”<br /> if (!this.canEqual(other)) {<br /> return false;<br /> }<br /> ColorPoint that = (ColorPoint) other;<br /> // 此步驟判斷“他”是否可以比較“我”<br /> if (!that.canEqual(this)) {<br /> return false;<br /> }</p><p> return new EqualsBuilder()<br /> .appendSuper(super.equals(that))<br /> .append(this.getColor(), that.getColor())<br /> .isEquals();<br /> }<br /> @Override<br /> public boolean canEqual(Object other) {<br /> // ColorPoint對象可以與所有繼承自ColorPoint的對象進行比較<br /> return (other instanceof ColorPoint);<br /> }<br />}

測試過程如下:
public static void main(String[] args) {<br /> Point p = new Point(1, 2);<br /> ColorPoint cp = new ColorPoint(1, 2, Color.RED);<br /> System.out.println(p.equals(cp)); // prints false<br /> System.out.println(cp.equals(p)); // prints false<br />}

Martin的方案很好的解釋了為什麼ColorPoint對象不能與Point對象進行比較,但這似乎並不能解決如何才能讓ColorPoint對象與Point對象進行比較的問題。利用canEqual是否可以完成這一目的呢?

補充說明

在這裡提供另一個類ColorPointEx,實現了可以與所有繼承自Point的對象進行比較的功能。並且比較是有選擇的:如果比較的對象也是繼承自ColorPointEx,則比較的內容包括Color資訊;如果比較的對象是繼承自 Point,則比較的內容不包括Color資訊。ColorPointEx的代碼實現如下:
import org.apache.commons.lang.builder.EqualsBuilder;<br />public class ColorPointEx extends Point {<br /> private final Color color;<br /> public ColorPointEx(int x, int y, Color color) {<br /> super(x, y);<br /> this.color = color;<br /> }<br /> public Color getColor() {<br /> return color;<br /> }<br /> @Override<br /> public boolean equals(Object other) {<br /> if (other == this) {<br /> return true;<br /> }<br /> if (!this.canEqual(other)) {<br /> return false;<br /> }<br /> if (other instanceof ColorPointEx) {<br /> // ColorPoint對象之間的比較<br /> ColorPointEx that = (ColorPointEx) other;</p><p> if (!that.canEqual(this)) {<br /> return false;<br /> }</p><p> return new EqualsBuilder()<br /> .appendSuper(super.equals(that))<br /> .append(this.getColor(), that.getColor())<br /> .isEquals();<br /> }</p><p> if (other instanceof Point) {<br /> // Point對象之間的比較<br /> Point that = (Point) other;</p><p> if (!that.canEqual(this)) {<br /> return false;<br /> }</p><p> return new EqualsBuilder()<br /> .appendSuper(super.equals(that))<br /> .isEquals();<br /> }</p><p> return false;<br /> }<br /> @Override<br /> public boolean canEqual(Object other) {<br /> return (other instanceof ColorPointEx<br /> || other instanceof Point<br /> );<br /> }<br />}

測試代碼如下:
public static void main(String[] args) {<br /> Point p = new Point(1, 2);<br /> ColorPoint cp = new ColorPoint(1, 2, Color.RED);<br /> ColorPointEx cpe = new ColorPointEx(1, 2, Color.BLUE);<br /> ColorPointEx cpe2 = new ColorPointEx(1, 2, Color.RED);<br /> System.out.println(p.equals(cp)); // prints false<br /> System.out.println(cp.equals(p)); // prints false </p><p> System.out.println(p.equals(cpe)); // prints true<br /> System.out.println(cpe.equals(p)); // prints true<br /> System.out.println(cp.equals(cpe)); // prints false<br /> System.out.println(cpe.equals(cp)); // prints false</p><p> System.out.println(cpe.equals(cpe2)); // prints false<br /> System.out.println(cpe2.equals(cpe)); // prints false<br />}

可以說,Martin的canEqual方案可以解決繼承關係下的對象比較問題,但前提是你必須正確的使用它。

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.