Effective Java 2

Item 10 遵守覆盖equals的约定

1、当类需要一个 逻辑相等 的功能时 覆盖equals()。

2、需要满足的性质: 自反性、对称性、传递性、一致性,参数为null时返回False。

3、没有办法在不违反equals约定的情况下,去通过添加新的值域来扩展一个实体类(子类化)。

4、使用复合来代替继承。

// Adds a value component without violating the equals contract
public class ColorPoint {
private final Point point;
private final Color color;
public ColorPoint(int x, int y, Color color) {
point = new Point(x, y);
this.color = Objects.requireNonNull(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);
}
... // Remainder omitted
}

5、不要使equals()依赖不可靠的资源。

6、写一个高质量equals方法的步骤:

  1)使用 == 操作符检查参数是否是本对象的一个引用。

  2)使用 instanceof 操作符检查参数是否是正确类型(因为这里是覆盖,方法的接收参数类型与Object中的一样,为Object,因此需要在方法体中检查参数类型)。

  3)将参数类型强制成正确类型。

  4)对于类中每个重要的域,依次对照检查。(对于非float和double的基础类型,使用 == 操作符。Float.compare(float,float),double同理)。

7、该方法的性能和比较顺序有关,尽可能的从最可能不同的域开始比较。

8、每次写完这个方法,最好进行单元测试。

// Class with a typical equals method
public final class PhoneNumber {
  private final short areaCode, prefix, lineNum;
  public PhoneNumber(int areaCode, int prefix, int lineNum) {
    this.areaCode = rangeCheck(areaCode, 999, "area code");
    this.prefix = rangeCheck(prefix, 999, "prefix");
    this.lineNum = rangeCheck(lineNum, 9999, "line num");
  }
private static short rangeCheck(int val, int max, String arg) {
  if (val < 0 || val > max) throw new IllegalArgumentException(arg + ": " + val);
  return (short) val;
  }
@Override public boolean equals(Object o) {
  if (o == this)
    return true;
  if (!(o instanceof PhoneNumber))
    return false;
  PhoneNumber pn = (PhoneNumber)o;
  return pn.lineNum == lineNum && pn.prefix == prefix && pn.areaCode == areaCode;
  }
... // Remainder omitted
}

9、当覆盖了equals()方法时必须同时覆盖hashCode()方法。

10、不要使equals()太复杂。

Item 11 当覆盖了equals()方法时必须同时覆盖hashCode()方法

1、hashCode()的约定:

  1)当在一个没有变化的类中多次调用hashCode()需要保证返回值相同。

  2)当两个对象equals()返回true时,他们的hashCode()应该相同。

  3)当两个对象equals()返回false时,他们的hashCode()尽量满足不同·(即需要一个好的散列函数)。

2、覆盖hashCode()的步骤:

  1)声明一个 int 型的变量 result,将它初始化为对应第一个重要域的hashcode c。

  2)对于每一个重要域 f ,计算其对应的hashcode。

    ① 如果是基础类型,计算Type.hashCode(f),其中 Type 是 f 的包装起类型。

    ② 如果是对象引用,并且这个类的equals()方法是通过递归的对重要域进行比较。那么也递归的调用hashCode()。

    ③ 如果是null,通常使用0。

    ④ 如果是数组,如果其中的元素都是重要的,使用Arrays.hashCode;如果部分重要对每个元素进行hashCode的递归;如果没有重要的,通常是一个非0常数。

  3)使用 result = 31 * result + f;进行处理,并作为最终结果返回。

3、应当忽略那些间接计算出来的域。

4、该方法对域的顺序是敏感的,否则同字异序列String 将返回相同的值

// Typical hashCode method
@Override public int hashCode() {
    int result = Short.hashCode(areaCode);
    result = 31 * result + Short.hashCode(prefix);
    result = 31 * result + Short.hashCode(lineNum);
    return result;
}

5、对于一些比较简单的类,可以直接使用以下语句,虽然性能会比较差:

// One-line hashCode method - mediocre performance
@Override public int hashCode() {
    return Objects.hash(lineNum, prefix, areaCode);
}

6、对于可能经常需要使用此方法,对其进行缓存,并在第一次调用时才进行计算。

// hashCode method with lazily initialized cached hash code
private int hashCode; // Automatically initialized to 0

@Override public int hashCode() {
    int result = hashCode;
    if (result == 0) {
        result = Short.hashCode(areaCode);
        result = 31 * result + Short.hashCode(prefix);
        result = 31 * result + Short.hashCode(lineNum);
        hashCode = result;
    }
    return result;
}                

7、不要为了方便试图故意遗漏某些重要域。

猜你喜欢

转载自www.cnblogs.com/WutingjiaWill/p/9180600.html