等价关系
等价性,其实我们一直有接触。
在集合论中,我们其实学过等价的具体定义:
等价关系:等价是指对于关系E ⊆ T x T ,它满足:
- 自反性: E(t,t) ∀ t ∈ T
- 对称性: E(t,u) ⇒ E(u,t)
- 传递性: E(t,u) ∧ E(u,v) ⇒ E(t,v)
JAVA中的“==”与“equals()"
这两者是不同的。
“==” 判断两个对象是否指向了存储空间中的同一个地址,而“equals()"判断两个对象的值是否相等。
当然,对于基本数据类型(如int,不是一个对象), “==”就表示值是否相等
不可变类型(immutable)的相等
Object类其实已经提供了equals()方法了,不过效果和 == 完全一样,
public class Object {
...
public boolean equals(Object that) {
return this == that;
}
}
所以,对于每一个新创建的类,我们都需要重写该方法。注意,重写需要加上@Override
,否则,只是对该方法进行了参数不同的重载(overload)
public class Duration {
private final int mins;
private final int secs;
// Rep invariant:
// mins >= 0, secs >= 0
// Abstraction function:
// AF(min, secs) = the span of time of mins minutes and secs seconds
/** Make a duration lasting for m minutes and s seconds. */
public Duration(int m, int s) {
mins = m; secs = s;
}
/** @return length of this duration in seconds */
public long getLength() {
return (long)mins*60 + secs;
}
}
对上面这个类进行重写equals():
@Override
public boolean equals(Object that) {
return that instanceof Duration && this.sameValue((Duration)that);
}
// returns true iff this and that represent the same abstract value
private boolean sameValue(Duration that) {
return this.getLength() == that.getLength();
}
这里的重写,先判断是否是Duration类型的对象,然后判断值是否相等。
注意,重写equals(),需要遵循这几个规定:
- equals 必须定义一个等价关系。即一个满足自反性、对称性和传递性关系。
- equals 必须是确定的。即连续重复的进行相等操作,结果应该相同。
- 对于不是null的索引x, x.equals(null) 应该返回false。
- 如果两个对象使用 equals 操作后结果为真,那么它们各自的hashCode 操作的结果也应该相同。
其中,第四个原则是接下来讨论的重点。
也就是说,我们在重写equals()的同时,还需要重写hashCode(),需要让两个相等的对象返回相同的哈希值。
@Override
public int hashCode() {
return (int) getLength();
}
可变类型(mutable)的相等
我们来定义两种不同的相等:
- 观察相等(observational equality):两个引用所指向的对象在当前相等,所有值都相同。但无法保证,在改变了其中的一个对象的值之后,这两个对象是否还能保持相同
- 行为相等(behavioral equality):两个引用所指向的对象始终相等,即使只改变其中的一个值,另一个的值也会跟着变化,从而保持始终相等
简单来说,观察相等,就是观察此时此刻两个引用的值是否相等,而行为相等,就是看这两个引用是否指向同一个对象,也就是我们之前说过的alias
比如,在下图中,d2和o2就是行为相等,因为两个引用指向了同一个对象,所以它们始终都会相等
需要注意的是,在JAVA中,对于可变数据类型,通常采用的是观察相等,比如判断两个List是否相等,看的是它们所包含的对象及顺序是否相等。
不过需要注意的是,可变类型需要采用行为相等