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、不要为了方便试图故意遗漏某些重要域。

 Item 12 总是覆盖toString()

1、如果你不覆盖该方法,它将返回类似 PhoneNumber@163b91 @之后的为其hash值,其可读性很差。

2、即使没有显式调用toString方法,在使用 println、printf、字符串拼接、断言或者debug的信息打印都会自动调用。

3、toString()方法需要包含类中所有重要的信息。

4、通过注释具体说明toString()的输出格式。

5、在静态工具类与绝大多数枚举类型中使用toString()是没有意义的,但是在任何子类含有相同字符串的抽象类中需要。

/**
* Returns a brief description of this potion. The exact details
* of the representation are unspecified and subject to change,
* but the following may be regarded as typical:
*
* "[Potion #9: type=love, smell=turpentine, look=india ink]"
*/
@Override public String toString() { ... }

以上三种方法 在AutoValue 或者IDE中都有相应的自动生成与支持。

 

Item 13 谨慎地覆盖clone

1、Cloneable接口用来被声明该类允许被克隆,但它不包含任何方法。它用于决定 Object's protected clone方法的实现行为。如果一个类实现了该接口。这个方法返回一个copy.

2、实际中,一个实现了Cloneable接口的方法,会被期望提供一个合适的public clone 方法,而要达到这点,这个类及其超类需要遵守一个复杂,脆弱的协议。主要原因是,它没有调用构造器而创建了一个对象。

3、其约定包括:

  

x.clone() != x                                    //true
x.clone().getClass() == x.getClass()   //true, but these are not absolute requirements
x.clone().equals(x)                            //true, this is not an absolute requirement
x.clone().getClass() == x.getClass()  //the object returned by this method should be obtained by calling
//super.clone. If a class and all of its superclasses (except Object) obey
//this convention,

 省略部分

4、Object‘clone 是不线程同步的。

5、任何实现 Cloneable 的类应该使用一个public方法覆盖clone,并且返回类型是这个类本身。这个方法必须先调用super.clone,然后对某些域进行修正。

6、如果这个类只包含基本类型或者一些不可变对象的引用,通常是不需要被修正的。而类似唯一的ID之类的信息例外。

7、一个更好的方法对对象进行复制,是使用一个复制构造器或者复制工厂。

// Copy constructor
public Yum(Yum yum) { ... };

// Copy factory
public static Yum newInstance(Yum yum) { ... };

8、除了数组使用clone方法比较适合,其他类型都最好使用复制构造器或者复制工厂。

 

Item 14 考虑实现Comparable

1、compareTo方法是Comparable接口中的唯一方法。

2、如果创建的一个值类有明显的自然顺序,应该考虑实现Comparable接口。

public interface Comparable<T> {
int compareTo(T t);
}

3、compareTo方法的约定包括:根据大于小于和等于返回1、-1、0;交换性(添加负号);传递性;替换性。

4、强烈建议使其结果与equals方法逻辑相同,如果不同请明显的注释出来。

5、通常情况下该方法使用在普通类型应抛出  ClassCastException。

6、一个类违反compareTo约定,可能导致其他依赖此方法的类放生错误。例如 TreeSet、TreeMap、Collections、Arrays。

7、和equals一样,没有办法在保证compareTo的约定情况下,通过添加一个值域来扩展一个实体类。

8、解决办法同样是使用复合而非继承。使其包含第一个类的一个实体,并提供一个视图方法返回第一个类。

9、与equals不同,其参数是有类型的,因此不需要检查类型。

10、如果一个域没有实现Comparable接口或是使用非标准排序,使用 Comparator。

11、一般形式:

// Single-field Comparable with object reference field
public final class CaseInsensitiveString implements Comparable<CaseInsensitiveString> {
    public int compareTo(CaseInsensitiveString cis) {
    return String.CASE_INSENSITIVE_ORDER.compare(s, cis.s);
    }
... // Remainder omitted
}

12、比较次序是关键的,请从最重要的域开始比较:

// Multiple-field Comparable with primitive fields
public int compareTo(PhoneNumber pn) {
    int result = Short.compare(areaCode, pn.areaCode);
    if (result == 0) {
        result = Short.compare(prefix, pn.prefix);
        if (result == 0)
            result = Short.compare(lineNum, pn.lineNum);
    }
    return result;
}            

13、Java 8开始,comparator 可以用来实现compareTo方法:

// Comparable with comparator construction methods
private static final Comparator<PhoneNumber> COMPARATOR =comparingInt((PhoneNumber pn) -> pn.areaCode).thenComparingInt(pn -> pn.prefix).thenComparingInt(pn -> pn.lineNum);

public int compareTo(PhoneNumber pn) {
    return COMPARATOR.compare(this, pn);
}
// Comparator based on static compare method
static Comparator<Object> hashCodeOrder = new Comparator<>() {
    public int compare(Object o1, Object o2) {
        return Integer.compare(o1.hashCode(), o2.hashCode());
    }
};
// Comparator based on Comparator construction method
static Comparator<Object> hashCodeOrder =Comparator.comparingInt(o -> o.hashCode());

 

posted @ 2018-06-13 22:41  wutingjia  阅读(306)  评论(0编辑  收藏  举报