Chapter 3 Methods Common to All Objects
Item 10: Obey the general contract when overriding equals
重写 equals() 方法时要遵守通用的约定。
Cases where avoid overriding euqals
当某个类的实例只与自己相等的时候,不要重写equals方法。例如下面的情况:
- Each instance of the class is inherently unique. 每个实例本质上是独一无二的,例如
Thread的实例。 - There is no need for the class to provide a "logical equality" test. 某个类不需要提供【逻辑相等】的测试。
- A superclass has already overridden
equals, and the superclass behavior is appropriate for this class. 父类已经重写了equals方法,并且父类的行为与子类适配。 - The class is private or package-private, and you are certian that its
equalsmethod will never be invoked. 类是私有的或者是包私有的,并且确定它的equals方法永远也不会被调用。
occasions where sholud to override equals
- 某个类包含逻辑相等(logical equality)的概念,并且父类没有重写
equals方法。例如,String和Integer等等。 - 当值类(Value Class)使用了实例控制(Instance Control)来保证每个值仅有一个实例的时候,例如
Enum枚举类,不需要重写equals方法
Propertis of equals method
equals方法的性质:
- 自反性 (Reflexive):对于非空的引用
o,表达式o.equals(o)的值必须为true - 对称性(Symmetric): 对于非空的引用
x | y,表达式x.equals(y)和y.equals(x)的值相同 - 传递性(Transitive): 对于非空引用
x, y, z,如果x.equals(y) && y.equals(z),那么x.equals(z) - 一致性(Consistence): 对于任意的非空引用
x, y,如果用于比较的equals的内容没有修改,那么无论经过多少次调用x.equals(y)返回值都应当保持不变 - 非空性(Non-nullity): 所有的非空对象都不应该和
null相等
无论一个类是否是可变的,都不应该写一个依赖于不可靠资源的equals方法。
Recipe for a high-quailty equals method
- Use the
==operator to check if the argument is a reference to this object. 使用==运算符检查此参数是否为该对象的引用 - Use the
instanceofoperator to check if the argument has the correct type. 使用instanceof运算符检查参数是否是正确的类型。 - Cast the argument to the correct type. 将参数转换为正确的类型。
- For each "significant" field in the class, check if that field of the argument mathes the corresponding field of this object. 对于类中的每个重要 字段 , 都要检查参数的字段和对象中相应的字段是否匹配。
- 对于基本数据类型,除了
double & float,使用==比较两个字段的值 - 对于引用数据类型,使用
equals()方法标胶 - 对于
double & float,使用Double.compare(double, double) or Float.compare(float, float) - 对于数组,将这些准则应用于数组中的每个元素。
- 如果某些引用字段中合法含有
null值,使用静态方法Objects.equals(o1, o2)
- First compare fields that are more likely to differ, less expensive to compare.比较的顺序会影响
equals的性能,所以优先比较那些最可能不同或者比较代价很小的字段。 - 吾日三省吾身:这个
equals方法具有对称性吗?具有传递性吗?具有一致性吗? - Always override
hashCodewhen you overrideequals. 重写equals方法之后,一定要重写hashCode方法 - Do not try be too clever. 不要让
equals方法太聪明。 - Do not substitute another type for
Objectin theequalsdeclaration. 在equals方法的声明中,不要将参数Object替换为其他类型的参数。
Item 11: Always override hashcode when you override equals
重写 equals方法的时候,也要重写 hashCode方法
The contract, adapted from the Object specification.
- the
hashCodemethod must consistently return the same value when it is invoked repeatedlly - the
hashCodemethod must produce the same Integer result if two objects are equal accrodiang to theeuqalsmethod - If two object are unequal according to the
equalsmethod, it is not required that their hashcode method return the same value
write a good hash function
- A good one tends to produce unequal hash codes to unequal instances
Approach I
steps to get a hash funciton
- 声明一个
int类型的result - 对于对象中的所有重要属性,一一遍历得到其哈希码
-
- 如果该属性是基本数据类型,使用
Type.hashCode(T),其中T为基本类型的包装类 - 如果该字段是一个引用数据类型,并且引用对象递归调用
equals来比较是否相同,则递归调用其hashCode方法。如果该引用为null,使用0值作为哈希值 - 如果字段是一个
array,则递归计算其中每个元素的hashCode
- 如果该属性是基本数据类型,使用
- 将第二步计算得出的哈希码计算为:
- 返回
result
编写完哈希函数之后,一定要使用单元测试验证两个相等的对象是否相等。
更加优质的哈希函数可以参阅com.google.common.hash.Hashing[Guava]类。
Approach II
Object类中有一个静态方法hash,可以为任意数量的参数返回一个哈希码。在性能要求不是很高的情况下,可以说调用此函数来重写对象中的hashCode方法
typescript
@Override
public int hashCode() {
return Object.hash(field1, field2, ...);
}
Approach III
如果一个类是不可变的, 并且其哈希码的计算复杂度比较高,可以设法使用懒加载的方式,在首次调用hashCode方法的时候,计算出哈希码,并缓存在对象中。
Some importance tips
- Do not be tempted to exclude significant fields from the hash code computation to improve performance. 不要省略重要字段的哈希值计算。
- Do not provide a detailed specification for the value returned by
hashCode, so clients can not reasonably depend on it; this give you the flexibility to change it. 不要给hashCode提供具体的规范,应当保持hashCode方法的灵活性。
Item 12: Always override toString
始终重写toString方法
toString的约定规范为:
a concise but informative representation that is easy for a person to read.
- 提供一个优秀的
toString实现可以让你的类更加易于使用和调试。 - the
toStringmethod should return all of the interesting information contained in the objejct.toString方法应该返回对象包含的所有感兴趣的信息。
决定是否为toString的返回值指定格式:
- 好处
-
- 可读性比较好,可以用于输出以及持久化数据对象
- 编码的时候,可以在字符串与对象之间随意切换
- 坏处
-
- 丢失了灵活性,一旦未来想要重新修改格式,会破坏现有的数据和代码
在静态工具类、枚举类中重写toString是没有意义的,但是可以在抽象类中重写toString方法,子类可以共享父类的公共字符串。
arduino
abstract class Animal {
private String name;
public Animal(String name) {
this.name = name;
}
// 抽象类中的抽象方法
abstract void makeSound();
// 重写toString方法
@Override
public String toString() {
return "Animal{" +
"name='" + name + ''' +
", sound='" + makeSound() + ''' +
'}';
}
}
class Dog extends Animal {
public Dog(String name) {
super(name);
}
@Override
void makeSound() {
return "Bark";
}
}
Item 13 Override clone judiciously
谨慎重写 clone方法
Cloneable接口的目的就是作为对象的一个mixin接口,标识该对象是允许克隆的。
实现Cloneable接口的类就是为了提供一个功能适当的公有 的clone方法。
一个对象的克隆(clone)应当满足如下的表达式:
x.clone != xx.cone().getClass() == x.getClass()x.clone.equals(x)
实现对象拷贝的方式
Approach I Implement Cloneable interface and override clone method
所有实现了Cloneable接口的类都应该覆盖clone方法,并且是公有的方法,他的返回类型为类本身。
- 调用
super.clone()方法 - 修正任何应该修正的域
Approach II-Provide a copy constructor or copy factory
arduino
public Yum(Yum yum) { ... } // Copy Constructor
public static Yum newInstance(Yum yum) { ... } // Copy static factory
优点
- 不依赖域某种有风险的、语言之外的创建机制
- 不需要强制存收文档规约
- 不会和
final类型的属性发生冲突 - 不会抛出不必要的受检异常
- 不需要进行强制类型转换
- 可以接受接口类型的参数
建议
除了复制数组之外,其他的对象都建议使用拷贝构造器或者静态工厂方法完成对象的拷贝。
Item 14: Consider implementing Comparable
考虑实现 Comparable接口
一个类实现了Comparable接口,就表明他的实例具有内在的排序关系。
实现Comparable接口的好处
- 集合中的实例可以方便地进行搜索、排序、计算
- 可以和泛型算法(generic algorithm)以及依赖于该接口的集合实现进行协作
需要遵循的规约
- 该对象大于、等于、小于指定对象的时候,分别返回一个正整数、零、负整数
- 指定对象的类型无法与该对象进行比较的时候,抛出
ClassCastException异常 - 确保自反性、对称性和传递性
一些比较好的建议
- 建议
(x.compareTo(y) == 0) == (x.equals(y)) - 对于基本数据类型的比较,使用包装类的
Box.compare静态方法,避免出错 - 如果一个类中多个字段需要比较,需要从最重要的字段开始比较,直到某一项结果的值不为0
TIPS
(1)通过 Comparator构建 CompareTo方法
可以在类中构建Comparator静态内部类,然后在CompareTo方法中调用静态方法,即可实现多个字段的比较,例如
arduino
// 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);
}
(2)比较两个整数的大小的时候,不要直接相减,这会造成整数的溢出,应该用静态的 compare方法
vbnet
// 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());
}
};