【effective Java读书笔记】对于所有对象都通用的方法(一)

来源:互联网 发布:淘宝双11外围流量大吗 编辑:程序博客网 时间:2024/06/05 12:40

一、覆盖equals时遵守通用约定

1)四种情况不需要覆盖equals

//4.类的equals方法是私有的或者是包级私有的public boolean equals(Object o) {//1.类每个实例本质都是唯一的        if (o == this)            return true;        if (!(o instanceof List))            return false;        //2.不关系逻辑相等;3.超类继承过来的行为对于子类也是合适的        ListIterator<E> e1 = listIterator();        ListIterator<?> e2 = ((List<?>) o).listIterator();        while (e1.hasNext() && e2.hasNext()) {            E o1 = e1.next();            Object o2 = e2.next();            if (!(o1==null ? o2==null : o1.equals(o2)))                return false;        }        return !(e1.hasNext() || e2.hasNext());    }
关于第4点注释:

如果类的equals方法,是私有的(或者包级私有的),则子类的equals方法与父类无关了。

2)覆盖equals方法需要遵守:自反性、对称性、传递性、一致性。

自反性:

对象引用还能不等于它自己么?

对称性:

书中举例:做一个能与字符串String比较的兼容大小写的字符串类型CaseInsensitiveString

public class CaseInsensitiveString {private final String s;public CaseInsensitiveString(String s) {if (s==null) {throw new NullPointerException();}this.s = s;}@Overridepublic boolean equals(Object obj) {if (obj instanceof CaseInsensitiveString) {//属于当前对象类型时return s.equalsIgnoreCase(((CaseInsensitiveString) obj).s);}if (obj instanceof String) {//注意:兼容String类型return s.equalsIgnoreCase((String) obj);}return false;}}
执行测试代码:

@org.junit.Testpublic void test2() {CaseInsensitiveString cis = new CaseInsensitiveString("Polish");String s = "polish";System.out.println(cis.equals(s));System.out.println(s.equals(cis));}
执行结果:

true

false

这样就不符合对称性,A等于B时,B不等于A这样子就很怪异。

解决方案:不兼容String类型。

@Overridepublic boolean equals(Object obj) {if (obj instanceof CaseInsensitiveString) {//属于当前对象类型时return s.equalsIgnoreCase(((CaseInsensitiveString) obj).s);}return false;}

传递性:

和上一个例子比较相似的一点是,也是因为强行兼容导致的灾难。区别,在于一个父子类继承关系。

父类:

public class Point {private int x;private int y;public Point(int x,int y) {this.x = x;this.y = y;}@Overridepublic boolean equals(Object obj) {if (!(obj instanceof Point)) {return false;}Point p = (Point) obj;return p.x==x && p.y==y;}}
子类:

public class ColorPoint extends Point {private Color color;public ColorPoint(int x, int y,Color color) {super(x, y);this.color = color;}@Overridepublic boolean equals(Object obj) {// if (!(obj instanceof ColorPoint)) {return false;}return super.equals(obj)&&((ColorPoint)obj).color==color;}}
看着毫无争议的写法。但是对称性考虑的时候,会发现:
@org.junit.Testpublic void test4(){Point p = new Point(1, 2);ColorPoint cp = new ColorPoint(1, 2, Color.RED);System.out.println(p.equals(cp));//违反了对称性System.out.println(cp.equals(p));}
执行结果:

true

false

但是,对于这个例子,和上一个不一样的地方在于,我们可以对ColorPoint这个子类做兼容处理

@Overridepublic boolean equals(Object obj) {if (!(obj instanceof Point)) {return false;}//如果被比较对象是Point但不是ColorPoint类型的话,则尝试返回反向比较:调用Point的equals方法,兼容Pointif (!(obj instanceof ColorPoint)) {return obj.equals(this);}return super.equals(obj)&&((ColorPoint)obj).color==color;}
想出这个反向比较的方案也算是很另类了。执行结果如下:

true

true

就在以为就此结束的时候,发现传递性没有了。看测试代码:

@org.junit.Testpublic void test4(){ColorPoint p1 = new ColorPoint(1, 2, Color.RED);Point p2 = new Point(1, 2);ColorPoint p3 = new ColorPoint(1, 2, Color.BLUE);System.out.println("p1等于p2----"+p1.equals(p2));System.out.println("p2等于p3----"+p2.equals(p3));System.out.println("p1等于p3----"+p1.equals(p3));}
执行结果:

p1等于p2----true

p2等于p3----true

p1等于p3----false

p1和p3由于都是ColorPoint所以会执行

returnsuper.equals(obj)&&((ColorPoint)obj).color==color;

所以当然不一样false。

结论:我们无法在扩展可实例化的类的同时,既增加值的组件,同时又保留equals约定。

所以,不要为了兼容其他类型(包括父类,子类型)去试图覆盖equals方法。这种情况,使用统一的父类的equals方法能处理是最好,例如abstarctList,abstarctMap。

解决方案一:

书中提及这样一种方案:(书中不推荐的方案)只有相同实现才去比较。例如上例子中,只有Point和Point比较,ColorPoint和ColorPoint比较。Point和ColorPoint比较则始终为false。

public class Point2 {private int x;private int y;public Point2(int x,int y) {this.x = x;this.y = y;}@Overridepublic boolean equals(Object obj) {System.out.println("obj.getClass()------"+obj.getClass());System.out.println("getClass()------"+getClass());//相同实现才可以比较if (obj==null||obj.getClass()!=getClass()) {return false;}Point2 p = (Point2) obj;return p.x==x && p.y==y;}}

然而这种不兼容的方案,从业务的角度上却又是不符合原则的。例如:

一个Point点集合:

public class UnitCircle {private static final Set<Point2> unitCircle;static{unitCircle = new HashSet<Point2>();unitCircle.add(new Point2(1, 0));unitCircle.add(new Point2(0, 1));unitCircle.add(new Point2(-1, 0));unitCircle.add(new Point2(0, -1));}public static boolean onUnitCircle(Point2 p){return unitCircle.contains(p);}}

一个Point子类:仅仅加了一个计数的值。直接使用父类的equals方法,却都无法正常比较使用。

public class CounterPoint extends Point2 {private static final AtomicInteger counter = new AtomicInteger();public CounterPoint(int x, int y) {super(x, y);counter.incrementAndGet();}public int numberCreated(){return counter.get();}}
测试代码:

@org.junit.Testpublic void test6(){CounterPoint cPoint = new CounterPoint(1, 0);System.out.println(UnitCircle.onUnitCircle(cPoint));}
执行结果:

false

因为两个类Point 和 ConuterPoint实现类不是一样的,所以始终都是false。

解决方案二:

利用复合,提供视图去比较:

//1.不再继承Pointpublic class ColorPoint3{private Color color;//2.写一个Point对象引用private Point point;public ColorPoint3(int x, int y,Color color) {if (color==null) {throw new NullPointerException();}point = new Point(x, y);this.color = color;}//3.对外公开一个point视图public Point getPoint() {return point;}@Overridepublic boolean equals(Object obj) {if (!(obj instanceof ColorPoint3)) {return false;}ColorPoint3 cp = (ColorPoint3) obj;return cp.point.equals(point)&&cp.color.equals(color);}}
执行代码:

@org.junit.Testpublic void test8(){ColorPoint3 cPoint = new ColorPoint3(1, 0,Color.RED);Point p = new Point(1, 0);System.out.println(cPoint.getPoint().equals(p));System.out.println(p.equals(cPoint.getPoint()));}
执行结果:

true

true

结论:

1、直接通过继承使用父类的equals方法。

2、使用复合处理;

3、使父类为抽象类,父类不能实例化。完全都通过之类的equals方法比较。



一致性:

针对引用型对象:如果两个对象相等,则需要一直相等。



















阅读全文
0 0