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());