Object类就是为了扩展用,其中非final方法(equals hashCode,toString,Clone,finalize)耳熟能详的方法在改写的时候必须要有很多通用约定,否则其他依赖于这些约定的类就无法与这些类正常工作。
1.equals方法
1.1什么时候该改写equals方法?
当一个类有自己的逻辑相等(而非对象身份)的概念,超类也没有改写equals以实现自己需要的行为时候,一般都是都是比较两个指向值对象的引用的时候,希望知道逻辑是否相等,而非指向同一个对象。
1.2 实现改写的通用约定
- 自反性: x.equals(x) 为true
- 对称性: x.equals(y) 为true,则 y.equals(x)也为true
下面这段代码coment部分的如果存在的话就违反了对称性,即equals方法中CaseInsensitiveString认识String类因此进行了判断,而反过来String类却人不认识CaseInsensitiveString类。这种不自觉的错误经常不经意就发生。
public class CaseInsensitiveString {
private String s;
public CaseInsensitiveString(String s) {
if(s == null) {
throw new NullPointerException();
}
this.s = s;
}
public boolean equals(Object o) {
if(o instanceof CaseInsensitiveString) {
return s.equalsIgnoreCase(((CaseInsensitiveString)o).s);
}
// 这段代码违反了对称性
// if(o instanceof String) {
// return s.equalsIgnoreCase((String)o);
// }
return false;
}
public static void main(String[] args) {
String s = "abc";
CaseInsensitiveString cis = new CaseInsensitiveString("Abc");
CaseInsensitiveString cis2 = new CaseInsensitiveString("ABC");
System.out.println("cis.equals(s)= " +cis.equals(s));
System.out.println("s.equals(cis) = " + s.equals(cis));
System.out.println("cis2.equals(cis) = " + cis2.equals(cis));
}
}
comment之前的结果是:
cis.equals(s)= true
s.equals(cis) = false
cis2.equals(cis) = true
comment段后的结果是:
cis.equals(s)= false
s.equals(cis) = false
cis2.equals(cis) = true
- 传递性: x.equals(y) 为true y.equals(z)为true, 则 x.equals(z)也为true
这个问题经常发生在继承扩展一个值的类的时候,如果继承扩展的类没有override equals则,调用equals的时候就会出现以下问题。
import java.awt.Color;
import java.awt.Point;
public class ColorPoint extends Point{
private static final long serialVersionUID = 1L;
private Color color;
public ColorPoint(int x, int y, Color color) {
super(x,y);
this.color = color;
}
public static void main(String[] args) {
// TODO Auto-generated method stub
Point p = new Point(1,2);
ColorPoint cp1 = new ColorPoint(1, 2, Color.red);
ColorPoint cp2 = new ColorPoint(1, 2, Color.black);
System.out.println("p.equals(cp1) =" + p.equals(cp1));
System.out.println("p.equals(cp2) =" + p.equals(cp2));
System.out.println("cp1.equals(cp2) =" + cp1.equals(cp2));
}
}
得到的结果是
p.equals(cp1) =true
p.equals(cp2) =true
cp1.equals(cp2) =true
但实际上cp1 和cp2是两个颜色不同的点。override equals方法后,虽然可以保证证对称性,但没有保证传递性,即p == p1 p==p2 得到的结果是p1 != p2.
@Override
public boolean equals(Object obj) {
// TODO Auto-generated method stub
if(!(obj instanceof Point)) {
return false;
}
// if obj is normal point do a color-blind comparasion
if(!(obj instanceof ColorPoint)) {
return this.equals(obj);
}
ColorPoint cp = (ColorPoint)obj;
return super.equals(obj) && (cp.color == this.color);
}
得到的结果是:
p.equals(cp1) =true
p.equals(cp2) =true
cp1.equals(cp2) =false
为了解决这个传递性的问题,一般要扩展可实例化的类的同时,既要增加新的特征,又要保留equals的约定,没有办法做到。
另外一种解决方案是 采用【复合】的方式。
即ColorPoint不是继承与Point,而是该类包含了一个Point的成员变量。
- 非空性: 非空引用值 x x.equals(null) 则返回false,非空不用多说了,就是上面override里面的加上null 的判断。
- 对于类中每个关键域,检查实参中与当前对象中对应的域是否匹配。
1.3检查equals方法的一些小结:
- 如果该类的类型是接口,则通过接口访问实参中的关键域
- 如果该类的类型是类,则访问实参中的关键域。
- 对于不是float 也不是double则可以直接用==比较,对于对象引用域则可以用递归的equals方法,
- 对于float则使用Float.floatToIntBits转换成int类型的值,再==比较
- 对于double则使用Double.doubleToLongBits转换成long型的值,再==比较
- 对于数组域,则以应用到元素上,有些对象引用域包含null是合法的,所以为了避免null异常,可以通过判断该field是不是null然后再进行常规的equals比较。
1.4 修改equals的时候一定要修改hashcode方法,否则违反了相等的对象必须有相等的散列码。
这个下一篇再记述。