Effective Java(二) 对于所有对象都通用的方法
2016-07-21 23:16
531 查看
本章将讲述何时以及如何覆盖这些非final类的Object方法(虽然Comparable.compareTo不是Object方法,因为类似也会涉及)
如果类具有自己特有的“逻辑相等”概念(不等同于对象等同),而且超类没有覆盖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约定
自反性的反例:
传递性的反例:
我们无法在扩展可实例化的类的同时,既增加新的值组件,同时又保留equals约定,除非愿意放弃面向对象的抽象所带来的优势。
如果在equals方法中使用getClass代替instanceof,可以扩展可实例化的类和增加新的值组件,同时保留equals约定:
这种方法只有当对象具有相同实现时,才能使对象等同,但是这在使用多态时可能会出现问题。例如HashSet的contains方法针对ColorPoint将永远返回false.
权宜之计
在ColorPoint中加入一个私有的Point域以及一个公有的视图方法:
注意,你可以在一个抽象类的子类中增加新的值组件,而不会违反equals约定。
实现高质量equals方法的诀窍:
1. 使用==操作符检查参数是否为这个对象的引用。如果是则返回true,这是一种性能优化,如果比较操作的代价可能很高,就值得这么做。
2. 使用instanceof检查参数是否为正确的类型。
3. 把参数转换为正确的类型。
4. 对于该类的每个关键域,检查参数对象的域是否与之匹配。
域的比较要比简单的等同性测试复杂得多。
域的比较顺序影响equals方法的性能,为了获得最佳性能,应该最先比较最有可能不一致的域,或者开销最低的域。
5. 当你编写了equals方法之后,应该问自己三个问题:是否时对称的、传递的、一致的?
6. 覆盖equals总要覆盖hashCode
7. 不要企图让equals方法过于智能,过度地寻求各种等价关系很容易陷入麻烦之中。
8. 不要讲equals的参数类型Object换成其他类型。
1. 只要对用的equals方法的比较操作所用到的信息没有修改,那么对同一对象调用多次hashCode方法返回值必须一致。
2. 相等的对象必须有相等的hashCode
3. 如果两个对象equals返回false,hashCode值不一定要产生不同的结果。但是不同对象产生不同的hashCode有可能提高散列表的性能。
hashCode的计算过程中可以把冗余域(根据其他域可以计算出来的域)排除在外。
必须排除equals方法中没有用到的域
不要试图从hashCode计算中排除一个对象的关键部分来提高性能。
第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接口
相关文章推荐
- MySQL多线程同步MySQL-Transfer介绍
- HTML基础知识整理
- 说说React的事(二)
- node.js 实现一个简单的登录拦截器
- CSS3的3D转换translate3d(x,y,z)函数
- 程序员都会的 35 个 jQuery 小技巧
- 改良与增加input的种类
- node.js AES/ECB/PKCS5Padding 与其他语言的加密解密通用
- express 解析post方式下的json参数
- HTML5(六)有序列表、无序列表、定义列表
- js调用大全含ajax
- js调用大全含ajax
- jQuery中的.bind()、.live()和.delegate()之间区别分析
- 前端设计模式责任链模式
- 【ReactNative】真机上无法调试 could not connect to development server
- LeetCode - 147. Insertion Sort List
- CSS3响应式布局总结
- jquery序列化表单
- javascript简单的轮播图
- js中的history和location对象及节点