您的位置:首页 > Web前端

Effective Java(二) 对于所有对象都通用的方法

2016-07-21 23:16 531 查看
本章将讲述何时以及如何覆盖这些非final类的Object方法(虽然Comparable.compareTo不是Object方法,因为类似也会涉及)

第8条 覆盖equals时请遵守通用约定

什么时候应该覆盖equals方法呢?

如果类具有自己特有的“逻辑相等”概念(不等同于对象等同),而且超类没有覆盖equals方法以实现期望的行为,这时应该覆盖equals方法。

有一种“值类”不需要覆盖equals方法,即用实例受控确保“每个值至多只存在一个对象”,枚举类型就属于这种。

equals方法的通用约定

自反性: 对于非null x,x.equals(x)

对称性: y.equals(x)—->x.equals(y)

传递性:x.equals(y), y.equals(z)—–>x.equals(z)

一致性:只要x和y的equals参数没有修改,x.equals(y)多次调用,返回值都一致。对于不可变对象,相等的对象永远相等,不相等的对象永远不相等。

“非空性”:对于任何非null的引用值x, x.equals(null) 必须返回false

所有的集合类都依赖于传递给它们的对象遵守equals约定

自反性的反例:

public final class CaseInsensitiveString{
private final String s;
public CaseInsensitiveString(String s){
if(s==null){
throw new NullPointerException();
this.s = s;
}
}

@Override
public boolean equals(Object o){
if(o instanceof CaseInsensitiveString){
return s.equalsIgnoreCase((CaseInsensitiveString)o)
}
if(o instanceof String){
return s.equalsIgnoreCase((String)o);
}
return false;
}

public static void main(String[] args){
CaseInsensitiveString cis = new CaseInsensitiveString("Polish");
String s = "polish";
cis.equals(s);//返回true
s.equals(cis);//返回false
}
}


传递性的反例:

public class Point{
private final int x;
private final int y;
public Point(int x, int y){
this.x = x;
this.y = y;
}

@Override
pubic boolean equals(Object o){
if(!(o instanceof Point)){
return false;
}
Point p = (Point)o;
return p.x == x && p.y == y;
}
}

public class ColorPoint extends Point{
private final Color color;
public ColorPoint(int x, int y, Color color){
super(x, y);
this.color = color;
}
@Override
public boolean equals(Object o){
if(!(o instanceof Point)){
return false;
}
if(!(o instanceof ColorPoint)){
return o.equals(this);
}
return super.equals(o) && ((ColorPoint)o).color = color;
}

public static void main(String[] args){
ColorPoint p1 = new ColorPoint(1,2,Color.RED);
Point p2 = new Point(1,2);
ColorPoint p3 = new ColorPoint(1,2,Color.BLUE);
p1.equals(p2)//返回true
p2.equals(p3)//返回true
p1.equals(p3)//返回false
}
}


我们无法在扩展可实例化的类的同时,既增加新的值组件,同时又保留equals约定,除非愿意放弃面向对象的抽象所带来的优势。

如果在equals方法中使用getClass代替instanceof,可以扩展可实例化的类和增加新的值组件,同时保留equals约定:

@Override
public boolean equals(Object o){
if(o==null||o.getClass()!=getClass()){
return false;
}
Point p = (Point)o;
return p.x == x && p.y == y;
}


这种方法只有当对象具有相同实现时,才能使对象等同,但是这在使用多态时可能会出现问题。例如HashSet的contains方法针对ColorPoint将永远返回false.

权宜之计

在ColorPoint中加入一个私有的Point域以及一个公有的视图方法:

public class ColorPoint{
private final Point point;
private final Color color;

public ColorPoint(int x, int y, Color color){
if(color == null){
throw new NullPointerException();
point = new Point(x, y);
this.color = color;
}
}

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

}


注意,你可以在一个抽象类的子类中增加新的值组件,而不会违反equals约定。

实现高质量equals方法的诀窍:

1. 使用==操作符检查参数是否为这个对象的引用。如果是则返回true,这是一种性能优化,如果比较操作的代价可能很高,就值得这么做。

2. 使用instanceof检查参数是否为正确的类型。

3. 把参数转换为正确的类型。

4. 对于该类的每个关键域,检查参数对象的域是否与之匹配。

域的比较要比简单的等同性测试复杂得多。

域的比较顺序影响equals方法的性能,为了获得最佳性能,应该最先比较最有可能不一致的域,或者开销最低的域。

5. 当你编写了equals方法之后,应该问自己三个问题:是否时对称的、传递的、一致的?

6. 覆盖equals总要覆盖hashCode

7. 不要企图让equals方法过于智能,过度地寻求各种等价关系很容易陷入麻烦之中。

8. 不要讲equals的参数类型Object换成其他类型。

第9条 覆盖equals时总有覆盖hashCode

hashCode的通用约定

1. 只要对用的equals方法的比较操作所用到的信息没有修改,那么对同一对象调用多次hashCode方法返回值必须一致。

2. 相等的对象必须有相等的hashCode

3. 如果两个对象equals返回false,hashCode值不一定要产生不同的结果。但是不同对象产生不同的hashCode有可能提高散列表的性能。

hashCode的计算过程中可以把冗余域(根据其他域可以计算出来的域)排除在外。

必须排除equals方法中没有用到的域

不要试图从hashCode计算中排除一个对象的关键部分来提高性能。

第10条 始终要覆盖toString

(略)

第11条 谨慎地覆盖clone

第12条 考虑实现Comparable接口

内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: