对于所有对象都通用方法的解读(Effective Java 第二章)
这篇博文主要介绍覆盖Object中的方法要注意的事项以及Comparable.compareTo()方法。
一、谨慎覆盖equals()方法
其实平时很少要用到覆盖equals方法的情况,没有什么特殊情况最好是使用原有提供的equlas方法。因为覆盖equals()方法时要遵循一些通用的约定之外,在与hash相关的集合类使用时,就必须要覆盖hashCode()方法了(第二点会强调)。
我们先说说覆盖equlas()方法要遵循哪些通用约定:
1、自反性:对于任何非null的引用值x, x.equals(x)必须返回true;
2、对称性:对于任何非null的引用值x和y,当且仅当y.equals(x)返回true时,x.equals(y)必须返回true;
3、传递性:对于任何非null的引用值x、y和z,当且仅当x.equals(y)返回true,并且y.equals(z)也返回true,那么x.equals(z)也必须返回true;
4、一致性:对于任何一个非null的引用值x和y,只要equals的比较操作在对象中所用的信息没有被修改,多次调用x.equals(y)的结果依然一致。;
5、非空性:对于任何非null的引用值,x.equals(null)必须返回false。 且x必须不能为空,否则会抛出空指针异常。
其实上面这些约定看起来都看简单但也不能大意,很有可能你只满足了其中一点或者几点没有满足全部。如果没有满足全部的话,那么你覆盖equals()方法是不合理的,总会有那么中情况会超乎你的意料之外。
下面我们举例说明,其中自反性是很难违反的(我也想象不出怎么举反例测试,如有高手明白,请举例评论下),下面我举例验证对称性:
package test.effective;
/**
* @Description: 普通坐标类
* @author yuanfy
* @date 2017年7月13日 上午10:30:06
*/
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 false;
}
Point p = (Point) obj;
return this.x == p.x && this.y == p.y;
}
}
/**
* @Description: 带有颜色的坐标
* @author yuanfy
* @date 2017年7月13日 上午10:35:19
*/
class ColorPoint extends Point { private String color; public ColorPoint(int x, int y, String color) {
super(x, y);
this.color = color;
}
/**
* 与point不满足对称性示范案例
*/
@Override
public boolean equals(Object obj) {
if (!(obj instanceof ColorPoint)) {
return false;//与point实例比较直接返回false
}
ColorPoint p = (ColorPoint) obj;
return super.equals(p) && this.color.equals(p.color);
}
} public class EqualsTest {
public static void main(String[] args) {
Point p = new Point(1,2);
ColorPoint cp = new ColorPoint(1, 2, "red"); System.out.println(p.equals(cp));//输出结果:true
System.out.println(cp.equals(p));//输出结果:false
}
}
从上面的例子可以看出是违反了对称性规定的。问题原因在于:在比较普通点和有色点时,忽略了颜色的比较,而有色点跟普通点比较时,普通点不属于ColorPoint的实例,就直接返回了false。所以ColorPoint类中覆盖equlas()方法是有问题的,修改equals()方法后的代码如下:
@Override
public boolean equals(Object obj) {
//不属于Point实例对象
if (!(obj instanceof Point)) {
return false;
}
//不是ColorPoint实例对象,可能是Point实例对象或者其他类型对象
if (!(obj instanceof ColorPoint)) {
return super.equals(obj);
}
//ColorPoint实例对象
ColorPoint p = (ColorPoint) obj;
return super.equals(p) && this.color.equals(p.color);
}
测试代码如下:
ColorPoint cp1 = new ColorPoint(1, 2, "red");
Point p = new Point(1,2);
ColorPoint cp2 = new ColorPoint(1, 2, "blue"); System.out.println(cp1.equals(p));//输出结果:true
System.out.println(p.equals(cp1));//输出结果:true System.out.println(p.equals(cp2));//输出结果:true
System.out.println(cp2.equals(p));//输出结果:true System.out.println(cp1.equals(p));//输出结果:true
System.out.println(p.equals(cp2));//输出结果:true
System.out.println(cp1.equals(cp2));//输出结果:false
从修改后的例子中可以看出,这种方法确实满足了对称性,但是却不满足传递性。其实我们无法在扩展可实例化的类的同时,既增加新的值组件,同时又保留equals约定,除非愿意放弃面向对象的抽象带来的优势。当然我们可以不扩展Point的,所谓“复合优先于继承”。在ColorPoint假如一个私有的Point域,代码如下:
class ColorPoint{
private String color;
private Point point;
public ColorPoint(int x, int y, String color) {
point = new Point(x, y);
this.color = color;
}
public Point asPoint(){
return point;
}
@Override
public boolean equals(Object obj) {
if (!(obj instanceof ColorPoint)) {//满足非空性验证
return false;
}
//ColorPoint实例对象
ColorPoint p = (ColorPoint) obj;
return this.point.equals(p.point) && this.color.equals(p.color);
}
}
public class EqualsTest1 {
public static void main(String[] args) {
ColorPoint cp1 = new ColorPoint(1, 2, "red");
Point p = new Point(1,2);
ColorPoint cp2 = new ColorPoint(1, 2, "red");
ColorPoint cp3 = new ColorPoint(1, 2, "red");
System.out.println(cp1.equals(p));//输出结果:false
System.out.println(p.equals(cp1));//输出结果:false
System.out.println(cp1.equals(cp2));//输出结果:true
System.out.println(cp2.equals(cp1));//输出结果:true
System.out.println(cp1.equals(cp2));//输出结果:true
System.out.println(cp2.equals(cp3));//输出结果:true
System.out.println(cp1.equals(cp3));//输出结果:true
}
}
上面的例子就满足对称性、传递性同时满足非空性。
接下来验证一致性:如果两个对象相等,它们就必须始终保持相等,除非他们中有一个对象或者两个都被修改了。换句话说,可变的对象在不同的时候可以与不同的对象相等, 而不可变对象则不会这样。比如说时间对象一个指定了时间,另外一个对象没有指定时间。当在某一个刻时候,他们既满足对称性和传递性,但是它不满足一致性。因为没有指定时间的对象的时间是一直在改变的。
所以当你编写完成了equlas之后,应该测试验证是否满足这个几个特性。
二、覆盖equals时总要覆盖hashCode
书中提出:在每个覆盖了equlas方法的类,也必须覆盖hashCode方法。如果不这样做的话, 就会违反Object.hashCode()的通用约定,从而导致该类无法结合所有基于散列的集合一起正常运作,这样的集合包括HashMap、HashSet和HashTable。
其中约定简洁如下(详细请参考书中):
1、相等的对象必须具有相等的散列码(hash code)
2、不相等的对象未必是不一样的散列码。也就是说相同散列码的两个对象,两个对象未必相等。但是相等的两个对象,一定就有相等的散列码。
下面根据例子来说明:
package test.effective; import java.util.HashMap;
import java.util.Map; public class PhoneNumber {
private int areaCode; private int prefix; private int lineNumer; public PhoneNumber(int areaCode, int prefix, int lineNumer) {
this.areaCode = areaCode;
this.prefix = prefix;
this.lineNumer = lineNumer;
} @Override
public boolean equals(Object obj) {
if (obj == this) {
return true;
}
if (!(obj instanceof PhoneNumber)) {
return false;
}
PhoneNumber pn = (PhoneNumber) obj;
return this.areaCode == pn.areaCode
&& this.prefix == pn.prefix
&& this.lineNumer == this.lineNumer;
} public static void main(String[] args) { Map<PhoneNumber, String> map = new HashMap<PhoneNumber, String>();
PhoneNumber pn1 = new PhoneNumber(408, 867, 5309);
PhoneNumber pn2 = new PhoneNumber(408, 867, 5309); System.out.println(pn1.equals(pn2));//输出结果:true map.put(pn1, "Jany"); System.out.println(map.get(pn1));
System.out.println(map.get(pn2));
}
}
我们将覆盖了equals方法的类结合基于散列码的集合使用。从上面例子39行知道pn1实例和pn2实例是相等,行43代码输出的结果可想而知是Jany,但是行44输出什么呢,估计没有仔细考虑的话,可能第一感觉也会输出null。我们看下HashMap中get方法源码就知道了。
public V get(Object key) {
if (key == null)
return getForNullKey();
Entry<K,V> entry = getEntry(key);
return null == entry ? null : entry.getValue();
}
final Entry<K,V> getEntry(Object key) {
if (size == 0) {
return null;
}
int hash = (key == null) ? 0 : hash(key);//获取对象的hash值
for (Entry<K,V> e = table[indexFor(hash, table.length)];
e != null;
e = e.next) {
Object k;
//hash必须相等才有可能进行返回
if (e.hash == hash &&
((k = e.key) == key || (key != null && key.equals(k))))
return e;
}
return null;
}
从源码中得知get()方法是要根据hash值获取的。然后我们在看看pn1和pn2对象的hash码。
System.out.println(pn1.hashCode());//
System.out.println(pn2.hashCode());//2031122075
所以显而易见见,上述map.get(pn2) 的返回值是为null的。
修正这个问题很简单,在PhoneNumber类中覆盖hashCode()方法即可,先提供简单的案例:
@Override
public int hashCode() {
return 1;
}
这样的能满足上面那个例子map.get(pn2) 返回“Jany”,但是这样 所有实例都是返回一样的hashCode是不可取的。参考书中最正确的方式:
@Override
public int hashCode() {
int result = 17;
result = 31 * result + areaCode;
result = 31 * result + prefix;
result = 31 * result + lineNumer;
return 1;
}
对于所有对象都通用方法的解读(Effective Java 第二章)的更多相关文章
- 对于所有对象都通用方法的解读(Effective Java 第三章)
这篇博文主要介绍覆盖Object中的方法要注意的事项以及Comparable.compareTo()方法. 一.谨慎覆盖equals()方法 其实平时很少要用到覆盖equals方法的情况,没有什么特殊 ...
- [Effective Java]第二章 创建和销毁对象
声明:原创作品,转载时请注明文章来自SAP师太技术博客( 博/客/园www.cnblogs.com):www.cnblogs.com/jiangzhengjun,并以超链接形式标明文章原始出处,否则将 ...
- 如何创建和销毁对象(Effective Java 第二章)
最近有在看Effective Java,特此记录下自己所体会到的东西,写篇博文会更加的加深印象,如有理解有误的地方,希望不吝赐教. 这章主题主要是介绍:何时以及如何创建对象,何时以及如何避免创建对象, ...
- Java高效编程之二【对所有对象都通用的方法】
对于所有对象都通用的方法,即Object类的所有非final方法(equals.hashCode.toString.clone和finalize)都有明确的通用约定,都是为了要被改写(override ...
- [Effective Java]第三章 对所有对象都通用的方法
声明:原创作品,转载时请注明文章来自SAP师太技术博客( 博/客/园www.cnblogs.com):www.cnblogs.com/jiangzhengjun,并以超链接形式标明文章原始出处,否则将 ...
- Effective Java:对于全部对象都通用的方法
前言: 读这本书第1条规则的时候就感觉到这是一本非常好的书.可以把我们的Java功底提升一个档次,我还是比較推荐的.这里我主要就关于覆盖equals.hashCode和toString方法来做一个笔记 ...
- [Effective Java 读书笔记] 第三章 对所有对象都通用的方法 第八 ---- 九条
这一章主要讲解Object类中的方法, Object类是所有类的父类,所以它的方法也称得上是所有对象都通用的方法 第八条 覆盖equals时需要遵守的约定 Object中的equals实现,就是直接对 ...
- Effective java -- 2 对于所有对象都通用到方法
第八条:覆盖equals时请遵守通用约定 什么时候需要覆盖equals方法?类具有自己的逻辑相等概念,并且父类的equals方法不能满足需要.重写equals时需要遵循一下约定: 自反性:非null ...
- 《Effective Java》第3章 对于所有对象都通用的方法
第8条:覆盖equals时请遵守通用约定 覆盖equals方法看起来似乎很简单,但是有许多覆盖方式会导致错误,并且后果非常严重.最容易避免这类问题的办法就是不覆盖equals方法,在这种情况下,类的每 ...
随机推荐
- 关于饿了么在浏览器标签页失去焦点时网页Title改变的实现方法
说在前面:必须是基于支持H5的浏览器才可以 这个 API 本身非常简单,由以下三部分组成. document.hidden:表示页面是否隐藏的布尔值.页面隐藏包括 页面在后台标签页中 或者 浏览器最小 ...
- 初入计算机图形学(二):对bidirectional path tracing的一些困惑
本人水平有限,若有错误也请指正~ 前文提及了光线追踪的一些常用手法,但是其中path tracing的实现最为简单,但是其最致命的一个缺点就是图像收敛速度很慢..原因在于从摄影机发射出的每一条光线若不 ...
- Predix Asset Service深度分析
前言 在IIOT领域,面临着保存海量数据的挑战,具体到Asset层面,则要保存物理对象,逻辑对象,复杂的关系,并支持对象间的组合,分类,标签和高效查询.总结来说,可以归纳为如下几种需求: 灵活的建 ...
- (整理)使用tomcat搭建HTTP文件下载服务器
本文是整理,非原创,由网络资料组成上自己踩的坑整理而成. 1. 假设需要下载的文件目录是D:\download1(注意这里写了个1,跟后面的名称区分) 2. 设置 tomcat 的虚拟目录.在 {to ...
- postgresql 多表联查
使用语句的先后顺序并不是优先级的排序: 连接分为:内连接和外连接,外连接分为左外连接,右外连接,全连接 概念上解释,表之间联合后数据如何整合. 返回的数据条数,可以通过集合求算.假如A集合有10条数据 ...
- 关于JQuery获取宽度和高度在chrome和IE下的不同
之前写了一个关于滚动条的东西,可是在写的时候发现JQuery在获取宽度和高度时在不同浏览器中是不一样的,下面发一下代码给给位看官先展示一下: $(function(){ $("#main&q ...
- JS 点击复制按钮 将文字复制到手机剪贴板
我们在制作移动端网页的时候,经常会遇到这样一个问题,如何点击一个"复制"按钮,把一串文字复制到手机剪贴板,如上图所示. 看了网上的一些方法后,感觉那些方法都太复杂,有点要用插件,有 ...
- Spring+SpringMVC+MyBatis深入学习及搭建(十二)——SpringMVC入门程序(一)
转载请注明出处:http://www.cnblogs.com/Joanna-Yan/p/6999743.html 前面讲到:Spring+SpringMVC+MyBatis深入学习及搭建(十一)——S ...
- 面向面试编程——javascript对象的几种创建方式
javascript对象的几种创建方式 总共有以下几个模式: 1.工厂模式 2.构造函数模式 3.原型模式 4.混合构造函数和原型模式 5.动态原型模式 6.寄生构造函数模式 7.稳妥构造函数模式 1 ...
- javascript中document.form[formName][]的意思
近来重新学习javascript发现还有很多知识点模糊,今天就javascript中的document.forms[formName][inputName]进行说明: <!DOCTYPE htm ...