Effective Java(第三版) 学习笔记 - 第三章 对于所有对象都通用的方法 Rule10~14
目录
Effective Java(第三版) 学习笔记 - 第三章 对于所有对象都通用的方法 Rule10~14
Rule10 覆盖equals时请遵守通用约定
我们都知道值相等和地址相等是有区分的,如果不重写equals方法的话,那么最终还是Object类中定义的 return (this == obj); 地址对比。如果遇到了我们需要自我实现equals方法,从而覆盖Object的equals时,需要满足以下几个约定:
equals需要遵守的约定
- 自反性:对于任何非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(x)也返回true,那么x.equals(z)也必须返回true。
- 一致性:对于任何非null的引用值x和y,只要equals的比较操作在对象中所用的信息没有被修改,多次调用x.equals(y)就会一致的返回true,或者一致的返回false。
- 非空型:对于任何非null的引用值x,x.equals(null)必须返回false。
针对这些要求拥有以下诀窍
- 优先比较引用值是否为null。如果是、则可以直接返回false。
- 使用==操作符检查“参数是否为这个对象的引用”。如果是、则可以直接返回true。
- 使用instanceof操作符检查“参数是否为正确的类型”。如果不是、则可以直接返回false。
- 把参数转换成正确的类型,然后对于该类中的每个关键域,检查参数中的域是否与该对象中对应的域相匹配。
- 编写完equals后,应该问自己是否满足了对称性、传递性、一致性。
什么情况下不需要重新定义equals
- 满足一下任意一点,那么我们就不需要重写Object中的equals方法:
- 类的每个实例本质上都是唯一的。
- 类没有必要提供“逻辑相等”功能。
- 超类(父类)已经覆盖了equals,超类的行为对于这个类也是合适的。
- 类是私有的,或者是包级私有的,可以确定它的equals方法永远不会被调用。
如果非要实现equals,建议听从以下的告诫
- 覆盖equals时总要覆盖hashCode(不然会对HashMap等类,使用时key的hash值判定出问题)。
- 不要企图让equals方法过于智能。
- 不要将equals申明中的Object对象替换成其他的类型(不然就不是重写Object中的equals方法)。
- 如果有可能,建议用Lombok的@EqualsAndHashCode(callSuper=true)直接实现。
Rule11 覆盖equals时总要覆盖hashCode
为什么要求重写hashCode方法,因为在涉及HashMap、HashTable、HashSet时,优先会比对key值的散列码(hashCode)。
如果不涉及到HashMap、HashTable、HashSet时,不重写hashCode方法不会有什么影响。但是建议在重写了equals之后,还是实现了hashCode,因为你根本无法知道未来是不是会出现涉及到HashMap的场景。
但是如果涉及到散列表操作,需要记住以下两点
- equals相等的两个对象,它们的hashCode也必须一定相等。
- hashCode相等的两个对象,他们并不一定真的相等。因为涉及到哈希冲突。
Rule12 始终要覆盖toString
public String toString() {
return getClass().getName() + "@" + Integer.toHexString(hashCode());
}
这是定义在Object中的toString实现,如果我们不重写toString方法的话,特别是针对POJO类,如果查看输出的打印日志,往往看见的不是我们想知道的具体属性值,而是类名+哈希码的十六进制字符串。
Rule13 谨慎地覆盖clone
Cloneable接口是一个空接口,感觉更像是一个标识定义。真正的clone方法是在Object中的native本地方法。
一般来讲不推荐实现clone方法,如果必须这么做,请记住:必须确保它不会影响到原始的对象,并确保正确的创建被clone对象中的约束条件。如果被clone的对象中包含引用对象,请确保该引用对象也实现了clone方法。
相比而言,提供一个拷贝构造器或者拷贝方法可能更加合适。
public Yum(Yum yum) {...};
public static Yum newInstance(Yum yum) {...};
Rule14 考虑实现Comparable接口
和clone方法相反,compareTo方法并不在Object中定义,而是定义在Comparable接口中。往往实现compareTo方法都是为了特定的排序关系。
实现compareTo最好遵守:
compareTo的类型必须相同
如果x.compareTo(y)为z,那么y.compareTo(x)必须是z的相反值
如果x.compareTo(y)>0 && y.compareTo(z)>0,那么必须x.compareTo(z)>0如果x.
如果x.compareTo(y)==0 && y.compareTo(z)==0,那么必须x.compareTo(z)==0
强烈建议(x.compareTo(y)==0) == (x.equals(y))
建议不要使用<>关系符来比较,尽可能的用Double.compare、Float.compare这种装箱类型去比较基本类型(注意:这是在第三版中特意提出与前两版不一样的地方)。或者使用Java8提供的比较器构造方法来实现。
// Multiple-field Comparable with primitive fields (page 69)
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;
}
// Comparable with comparator construction methods (page 70)
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);
}
不推荐利用数值相减的结果与0进行判断,因为十分有可能造成整数溢出。建议使用下面推荐的两种方法
// 不推荐,容易造成整数溢出
static Comparator<Object> hashCodeOrder = new Comparator<>() {
public int compare(Object o1, Object o2) {
return o1.hashCode() - o2.hashCode();
}
}
// 推荐1
static Comparator<Object> hashCodeOrder = new Comparator<>() {
public int compare(Object o1, Object o2) {
return Integer.compare(o1.hashCode(), o2.hashCode());
}
}
// 推荐2
static Comparator<Object> hashCodeOrder = Comparator.comparingInt(o -> o.hashCode());
本文技术菜鸟个人学习使用,如有不正欢迎指出修正。xuweijsnj