重載equals方法時要遵守的通用約定--自反性,對稱性,傳遞性,一致性,非空性


本文涉及到的概念
1.為什么重載equals方法時,要遵守通用約定
2.重載equals方法時,要遵守哪些通用約定
 
為什么重載equals方法時,要遵守通用約定
Object類的非final方法都有明確的通用約定,這些方法是被設計成被重載的。重載時,如果不遵守通用約定,那么,其它依賴於這些通用約定的類(例如HashMap和HashSet)就無法結合該類一起正常工作----<<effective java>>
 
 
quals方法實現了等價關系,重載時要遵守的通用約定:
a.自反性(reflexive)  對於任何非null的引用值x, x.equals(x)必須返回true。
b.對稱性(symmetric)  對於任何非null的引用值x和y,當且僅當y.equals(x)返回true時,x.equals(y)必須返回true
c.傳遞性(transitive)   對於任何非null的引用值x,y和z,如果x.equals(y)返回true,並且y.equals(z)返回true,那么x.equals(z)返回true
d.一致性      對於任何非null的引用值x和y,只要equals的比較操作在對象中所用的信息沒有被修改,多次調用x.equals(y)就會一致地返回
true,或者一致地返回false
e.對於任何非null的引用值x,x.equals(null)必須返回false
 
a.自反性
基本上不會違背這一條規定。如果違背了的話,將一個引用添加到一個集合中,然后,調用集合的contains(x)方法,它會返回false。x.equals(x)不等於true,導致contains(x)方法返回false。
 
b.對稱性
對於任何非null的引用值x和y, x.equals(y)返回true, y.equals(x)也要返回true
public final class CaseInsensitiveString {
private final String s;

public CaseInsensitiveString(String s) {
if (s == null)
throw new NullPointerException();
this.s = s;
}

// Broken - violates symmetry!
@Override
public boolean equals(Object o) {
if (o instanceof CaseInsensitiveString)
return s.equalsIgnoreCase(((CaseInsensitiveString) o).s);
if (o instanceof String) // One-way interoperability!
return s.equalsIgnoreCase((String) o);
return false;
}

// This version is correct.
// @Override public boolean equals(Object o) {
// return o instanceof CaseInsensitiveString &&
// ((CaseInsensitiveString) o).s.equalsIgnoreCase(s);
// }

public static void main(String[] args) {
CaseInsensitiveString cis = new CaseInsensitiveString("Polish");
String s = "polish";
System.out.println(cis.equals(s) + " " + s.equals(cis));
}
}
可以把上述的例子代碼,代入對稱性公式,CaseInsensitivesString為x, String為y, CaseInsensitivesString為y.
x.equals(y),y.equals(x)都為true,當y是CaseInsensitivesString類型時; 當y為String類型時,y.equals(x),就為false。
 
String類的equals方法的實現: stringInstance.equals(CanseInsensitivesStringIns),它會返回false,因為x不是String類型
public boolean equals(Object anObject) {
    if (this == anObject) {
        return true;
    }
    if (anObject instanceof String) {
        String anotherString = (String)anObject;
        int n = count;
        if (n == anotherString.count) {
        char v1[] = value;
        char v2[] = anotherString.value;
        int i = offset;
        int j = anotherString.offset;
        while (n-- != 0) {
            if (v1[i++] != v2[j++])
            return false;
        }
        return true;
        }
    }
    return false;
    }
c.傳遞性
equals預定的第三個要求是,如果一個對象等於第二個對象,並且第二個對象又等於第三個對象,則第一個對象一定等於第三個對象。
----<<effective java>>
重寫類的equals方法后,使用x.equals(y), y.equals(z),x.equals(z),去檢驗。如果發現不符合,則違反了該通用約定,那么,就要重新實現。
 
下面的例子,違反了傳遞性:
父類Point,Point類之間的比較,重寫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))
return false;
Point p = (Point) o;
return p.x == x && p.y == y;
}

// Broken - violates Liskov substitution principle - Pages 39-40
// @Override public boolean equals(Object o) {
// if (o == null || o.getClass() != getClass())
// return false;
// Point p = (Point) o;
// return p.x == x && p.y == y;
// }

// See Item 9
@Override
public int hashCode() {
return 31 * x + y;
}
}

創建一個子類,繼承Point類

import java.awt.Color;

public class ColorPoint extends Point {
private final Color color;

public ColorPoint(int x, int y, Color color) {
super(x, y);
this.color = color;
}

// Broken - violates symmetry!
//x為Point,y為ColorPoint
// x.equals(y)為true,但是y.equals(x)為false
@Override
public boolean equals(Object o) {
if (!(o instanceof ColorPoint))
return false;
return super.equals(o) && ((ColorPoint) o).color == color;
}

// Broken - violates transitivity!
// @Override public boolean equals(Object o) {
// if (!(o instanceof Point))
// return false;
//
// // If o is a normal Point, do a color-blind comparison
// if (!(o instanceof ColorPoint))
// return o.equals(this);
//
// // o is a ColorPoint; do a full comparison
// return super.equals(o) && ((ColorPoint)o).color == color;
// }
//p1.euqlas(p2),p2.equals(p3),p1.equals(p3)
//輸出結果:輸出結果 true, true ,false

public static void main(String[] args) {
// First equals function violates symmetry
Point p = new Point(1, 2);
ColorPoint cp = new ColorPoint(1, 2, Color.RED);
System.out.println(p.equals(cp) + " " + cp.equals(p));

// Second equals function violates transitivity
ColorPoint p1 = new ColorPoint(1, 2, Color.RED);
Point p2 = new Point(1, 2);
ColorPoint p3 = new ColorPoint(1, 2, Color.BLUE);
System.out.printf("%s %s %s%n", p1.equals(p2), p2.equals(p3), p1.equals(p3));
}
}
p1和p2的比較,不考慮顏色;p2和p3的比較,也不考慮顏色;p1和p3的比較考慮顏色,於是p1和p3不相等。違反了傳遞性原則,x.equals(y),y.equals(z).x.equals(z)
 
如何解決這個問題?
事實上,這是面向對象語言中關於等價關系的一個基本問題。我們無法在擴展可實例化的類的同時,既增加新的值組件,同時又保留equals約定,除非願意放棄面向對象的抽象所帶來的優勢。
也就是,我們無法在擴展父類,然后,同時在子類中保留父類和子類的equals約定。
 
解決辦法:
a.復合優先於繼承
b.父類是抽象類(沒有任何值組件),子類添加值域
 
使用復合的方式來解決
 
// Simple immutable two-dimensional integer point class - Page 37

import java.util.*;

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))
  return false;
  Point p = (Point)o;
  return p.x == x && p.y == y;
  }

  // See Item 9
  @Override public int hashCode() {
  return 31 * x + y;
  }
}
public enum Color { RED, ORANGE, YELLOW, GREEN, BLUE, INDIGO, VIOLET }

// Adds a value component without violating the equals contract - Page 40
public class ColorPoint {
  private final Point point;
  private final Color color;

  public ColorPoint(int x, int y, Color color) {
  if (color == null)
  throw new NullPointerException();
  point = new Point(x, y);
  this.color = color;
  }

  /**
  * Returns the point-view of this color point.
  */
  public Point asPoint() {
  return point;
  }

  @Override public boolean equals(Object o) {
  if (!(o instanceof ColorPoint))
  return false;
  ColorPoint cp = (ColorPoint) o;
  return cp.point.equals(point) && cp.color.equals(color);
  }

  @Override public int hashCode() {
  return point.hashCode() * 33 + color.hashCode();
  }
}

/////////////////////////////////////////////////////////////////////////
public class Test {

public static void main(String[] args) {
// First equals function violates symmetry
Point p = new Point(1, 2);
ColorPoint cp = new ColorPoint(1, 2, Color.RED);
System.out.println(p.equals(cp) + " " + cp.equals(p));

// Second equals function violates transitivity
ColorPoint p1 = new ColorPoint(1, 2, Color.RED);
Point p2 = new Point(1, 2);
ColorPoint p3 = new ColorPoint(1, 2, Color.BLUE);
System.out.printf("%s %s %s%n", p1.equals(p2), p2.equals(p3), p1.equals(p3));
}
}
輸出結果:
false false
false false false

 

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM