《Effective java》读书记录-第8条-覆盖equals时需要遵守通用约定

来源:互联网 发布:淘宝运费模板删除不了 编辑:程序博客网 时间:2024/06/05 05:46

覆盖equals方法看起来似乎很简单,但是有许多覆盖方式会导致错误。

1.类的每个实例本质上都是唯一的。

2.不关心类是否提供了“逻辑相等(logical equality)”的测试功能。

3.超类已覆盖equals,从超类继承过来的行为对于子类也是合适的。

4.类是私有的或是包级私有的,可以确定它的equals方法永远不会被调用。


equals方法实现了等价关系(equvialence relation):

1.自反性(reflexive)

对于任何非null的引用值x,x.equals(x)必须返回true。

下面的代码就违反了自反性,collection中的contains方法果断告诉你,明明存在的实例却不在collection中。

public class MyEquals {    private String name;        public MyEquals (String name) {       this.name=name;    }        @Override    public boolean equals(Object obj) {        if(obj instanceof MyEquals){            return this.reflexivityEquals(this,(MyEquals) obj);        }        return false;    }    /**     * 比较两个是否是一个对象     * 这是错误的比较方法,错误在匹配相同时也返回false     */    private boolean reflexivityEquals(MyEquals x,MyEquals y){        if(x==y){            return false;        }else {            return false;        }    }}public class MyEqualsTest extends TestCase {    public void testRelexivity(){        MyEquals myEquals1=new MyEquals("1");        Collection<myequals> cols=new ArrayList<myequals>();        cols.add(new MyEquals("2"));        cols.add(new MyEquals("3"));        cols.add(myEquals1);        if(cols.contains(myEquals1)){            System.out.println("TRUE");        }else{            System.out.println("FALSE");        }    }}</myequals></myequals>

2.对称性(symmetric)

对于任何非null的引用值x和y,y.equals(x) 返回true,x.equals(y)也必须返回true 。

下面的例子就出现了非对称的错误。

public class MyEquals {    private String name;    public MyEquals (String name) {       this.name=name;    }    @Override    public boolean equals(Object myEquals) {        if(obj instanceof MyEquals){            return this.symmtircEquals(this,(MyEquals) obj);        }        return false;    }    /**     *       *  比较时忽略大小写     *  这是错误的比较方法,错误就在只把x转成小写,没有考虑到y是大写的情况     */    private boolean symmtircEquals(MyEquals x,MyEquals y){        if(x.getName().toLowerCase().equals(y.getName())){            return true;        }else {            return false;        }    }      public String getName() {        return name;    }}public class MyEqualsTest extends TestCase {    public void testSymmetric(){        MyEquals x=new MyEquals("a");        MyEquals y=new MyEquals("A");        if(x.equals(y)){            System.out.println("x==y TRUE ");        }else{            System.out.println("x==y FALSE ");        }        if(y.equals(x)){            System.out.println("y==x TRUE ");        }else{            System.out.println("y==x FALSE ");        }    }}


3.传递性(transitive)

对于任何非null的引用值x、y和z,x.equals(y) 返回true,y.equals(z)也返回true,那么x.equals(z)也必须返回true。


public class Point {    private int x;    private int y;    public Point (int x,int y) {        this.x=x;        this.y=y;    }    @Override    public boolean equals(Object obj) {        if(obj instanceof  Point){           return x==((Point)obj).getX() && y==((Point)obj).getY();        }        return false;    }    public int getX() {        return x;    }    public int getY() {        return y;    }}public class PointColor extends  Point {    private String color;    public PointColor(int x, int y,String color) {        super(x, y);        this.color=color;    }    @Override    public boolean equals(Object obj) {        if (obj instanceof PointColor)            return super.equals(obj) && ((PointColor) obj).getColor()==this.color;        return false;    }    public String getColor() {        return color;    }}public class MyEqualsTest extends TestCase {    public  void testTransitivity(){        PointColor x=new PointColor(1,1,"1");        PointColor y=new PointColor(1,1,"1");        Point z=new Point(1,1);        if(x.equals(y)){            System.out.println("x==y TRUE ");        }else{            System.out.println("x==y FALSE ");        }        if(y.equals(z)){            System.out.println("y==z TRUE ");        }else{            System.out.println("y==z FALSE ");        }        if(z.equals(x)){            System.out.println("z==x TRUE ");        }else{            System.out.println("z==x FALSE ");        }    }}


4.一致性(consistent)

对于任何非null的引用值x和y,只要equals的比较操作在对象中所用的信息没有被修改,多次调用x.equals(y)就会一致地返回true,或者一致地返回false。

如果两个对象相等,它们就必须始终保持相等,除非它们中有一个对象(或者两个对象)被修改。换句话说,可变对象在不同时候可以与不同的对象相等,而不可变对象则不会这样(这里引出了一个不可变对象的概念,参考第15条)。 在编写一个类的时候,应该仔细考虑它是否应该是不可变的,如果认为它应该是不可变的,就必须保证equals方法满足这样的限制:相等的对象永远相等,不相等的对象永远不相等。

无论类是否是可变的,都不要使equals方法依赖于不可靠的资源。equals方法应该对驻留在内存中的对象执行确定的计算。java.net.URL的equals方法违反了equals约定,具体可以参看代码

    public boolean equals(Object obj) {        if (!(obj instanceof URL))            return false;        URL u2 = (URL)obj;        return handler.equals(this, u2);    }

5.非空性(Non-nullity)。对于任何非null的引用值x,x.equals(null)必须返回false。

一般不需要写特定的代码去判断,因为 instanceof的第一个参数为null,那么instanceof操作符都指定返回false。


总结:

1.使用==操作符检查“参数是否为该对象的引用”。如果是,返回true。这样可以优化性能,减少执行。

2.使用instanceof操作符检查“参数是否为正确类型”。如果不是,返回false。

3.把参数转换成正确类型。因为转换之前进行过instanceof策四,所以可以确保成功。

4.对于该类的每个“关键(significant)”域,检查参数中域是否与该对象中对应的域匹配

如果测试全部成功,返回true,否则返回false。

float和double这两个基本类型需要用Float.compare或Double.compare方法,其他的可以直接使用==操作符。

对于可以为null的域,为了避免NullPointException异常,习惯使用(field == null ? o.field==null :field.equals(o.field)),如果filed和o.filed通常是相同的对象引用,那么(field==o.field||(field != null && field.equals(o.field)))会更快。

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

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

最后的一些告诫:

覆盖equals时总要覆盖hashCode。

不要企图让equals方法过于智能。

不要将equals声明中的Object对象替换为其他的类型。







0 0
原创粉丝点击