你所忽略的,覆盖equals时需要注意的事项《effective java》
我们都知道Object的equals的比较其实就是==的比较,其实是内存中的存放地址的比较。正常逻辑上:类的每个实例本质上都是唯一的。
在工作中我们实际的业务逻辑往往有可能出现一些相对特殊的需求需要对equals方法进行重写,那么重写equals需要注意哪些规则或者通用的约定呢?
equals方法实现了等价关系(equivalence relation):
- 自反性(reflexive)。对于任何非
null的引用值x,x.equals(x)必须返回true。 - 对称性(symmetric)。对于任何非
null的引用值x和y,当且仅当y.equals(x)返回true时,x.equals(y)必须返回true。 - 传递性(transitive)。对于任何非
null的引用值x、y和z。如果x.equals(y)返回true,并且y.equals(z)也返回true,那么x.equals(z)也必须返回true。 - 一致性(consistent)。对于任何非
null的引用值x和y,只要equals的比较操作在对象中所用的信息没有被修改,多次调用x.equals(x)就会一致地返回true,或者一致的返回false。 - 对于任何非
null的引用值x,x.equals(null)必须返回false。
第一点自反性不需要多说,基本上不可能会出现违背这条约定的情况当自己和自己比较的时候返回false。
第二点对称性,这种情况还是有可能会出现的。我们可以假设一个场景,这个场景是我们创建一个类并且里面只有一个String属性字段,这个类需要实现的是可以不区分字符串的大小写。
pubilc final class IgnoreCaseString {
private final String s;
public IgnoreCaseString(String s) {
if (s == null)
throw new NullPointerException();
this.s = s;
}
@Override
public boolean equals(Object o) {
if (o instanceof IgnoreCaseString)
return s.equalsIgnoreCase(
((IgnoreCaseString) o).s);
if (o instanceof String)
return s.equalsIgnoreCase((String) o);
return false;
}
...//更多代码(重写equals就需要重写hashCode)
}
在这个类中,equals方法的意图非常好,它的企图是可以与普通的字符串对象进行互操作。但是这段代码无意中触犯了对称性这个约定,从new IgnoreCaseString(“Po”).equals("po")是为true的,但是反过来“po”.equals(new IgnoreCaseString("Po"))的结果是false。假如违反了这一情况而没有去更正,他会破坏已有的集合框架的一些方法,使其变的不在准确。
IgnoreCaseString ics = new IgnoreCaseString("Po");
List<IgnoreCaseString> list = new ArrayList<IgnoreCaseString>();
list.add(ics);
System.out.println(list.contains("po"));
List<String> list1 = new ArrayList<>();
list1.add("po");
System.out.println(list1.contains(ics));
结果是
,此时list.contains(s)会返回什么结果呢?没人知道,在Sun的当前实现中,它碰巧返回false,但这只是这个特定实现得出的结果而已。在其他的实现中,它有可能返回true(如上面代码中的list1.contains(ics)),或者抛出一个运行时(runtime)异常。一旦违反了equals约定,当其他对象面对你的对象时,你完全不知道这些对象的行为会这么样。
第三点传递性,equals约定:如果一个对象等于第二个对象,并且第二个对象又等于第三个对象,则第一个对象一定等于第三个对象。无意识违反这一情况其实不难想象,考虑子类的情形,子类增加的信息会影响到equals的比较结果。
public class TwoDCoordinate {
private final int x;
private final int y;
public TwoDCoordinate(int x, int y) {
this.x = x;
this.y = y;
}
@Override
public boolean equals(Object o) {
if (!(o instanceof TwoDCoordinate))
return false;
TwoDCoordinate p = (TwoDCoordinate)o;
return p.x == x && p.y == y;
}
...//更多代码(重写equals就需要重写hashCode)
}
public class ThreeDCoordinate extends TwoDCoordinate{
private final int z;
public ThreeDCoordinate(int x, int y, int z) {
super(x, y); this.z=z;
} @Override
public boolean equals(Object o) {
if (!(o instanceof TwoDCoordinate))
return false;
if (!(o instanceof ThreeDCoordinate))
return o.equals(this);
return super.equals(o) && this.z == ((ThreeDCoordinate) o).z;
}
...//更多代码(重写equals就需要重写hashCode)
}
ThreeDCoordinate t1 = new ThreeDCoordinate(1, 2, 3);
TwoDCoordinate t2 = new TwoDCoordinate(1, 2);
ThreeDCoordinate t3 = new ThreeDCoordinate(1, 2, 4);
System.out.println(t1.equals(t2));
System.out.println(t2.equals(t1));
System.out.println(t2.equals(t3));
System.out.println(t1.equals(t3));
结果是true true true false,上述代码已经满足了自反性和对称性的约定,但是没有满足传递性,t1.equals(t2)为true,t2.equals(t3)为true,t1.equals(t3)却为false。
这种情况很多程序员会犯。
想要避免这种情况,其实可以用getClass测试代替instanceof测试。如将TwoDCoordinate 的equals 方法改为
public boolean equals(Object o) {
if (o == null || o.getClass() != this.getClass())
return false;
TwoDCoordinate p = (TwoDCoordinate)o;
return p.x == x && p.y == y;
}
这种替代方式其实不会太糟糕,但是结果却不会太理想,暂时没有想到令人满意的办法实现既可以扩展又不可实例化的类,但是可以考虑复合优先于继承。
第四点一致性,equals约定的第四个要求是,如果两个对象相等,它们就必须始终保持相等,除非它们中有一个对象(或者两个都)被修改了。换句话说,可变对象在不同的时候可以与不同的对象相等,而不可变对象则不会这样。当你在写一个类的时候,应该仔细考虑她是否应该是不可变的。如果认为它应该是不可变的,就必须保证equals方法满足这样的限制条件:相等的对象永远相等,不相等的对象永远不相等。
你所忽略的,覆盖equals时需要注意的事项《effective java》的更多相关文章
- 第8条:覆盖equals时遵守通用约定
如果不需要覆盖equals方法,那么就无需担心覆盖equals方法导致的错误. 什么时候不需要覆盖equals方法? 1.类的每个实例本质上是唯一的. 例如对于Thread,Object提供的equa ...
- 第8条:覆盖equals时请遵守通用约定
第8条:覆盖equals时请遵守通用约定 引言:尽管Object是一个具体类,但是设计它主要是为了拓展.它所有的非final方法(equals.hashCode.toString.clone和fina ...
- Item 9 覆盖equals时总要覆盖hashCode
为什么覆盖equals时,总要覆盖hashCode? 原因是,根据Object规范: 如果两个对象根据equals(Object)方法比较是相等的,那么调用这两个对象中任意一个对象的hashCod ...
- Item 8 覆盖equals时请遵守通用约定
在覆盖equals方法的时候,你必须要遵守它的通用约定,不遵守,写出来的方法,会出现逻辑错误.下面是约定的内容: equals方法实现了等价关系: 自反性.对于任何非null的引用值,x.eq ...
- 第9条:覆盖equals时总要覆盖hashCode
在每个覆盖equals方法的类中,也必须覆盖hashCode方法.否则,会违反Object.hashCode的通用约定,从而导致该类无法结合所有基于散列的集合一起正常工作,包括HashMap,Hash ...
- 第八条:覆盖equals时请遵守通用约定
==是物理相等 equals是逻辑相等 因为每个类的实例对象本质上都是唯一的 ,利用物理相等(==)是指一个实例只能相等于它自己. 利用逻辑相等是(equals)指 一个实例是否和另一个实例的某些关键 ...
- 覆盖equals时请遵守通用约定
Object类中非final修饰的方法有equals().hashCode().toString().finalize().clone()1.equals()方法不需要被覆盖的情况:1)实例化的对象只 ...
- Effective Java —— 覆盖equals时遵守通用约定
本文参考 本篇文章参考自<Effective Java>第三版第十条"Obey the general contract when overriding equals" ...
- Effective Java —— 覆盖equals时总要覆盖hashCode
本文参考 本篇文章参考自<Effective Java>第三版第十一条"Always override hashCode when you override equals&quo ...
随机推荐
- Android 光标位置设置
EditText edit =(EditText) findViewById(R.id.etTest); 1.设置光标在EditText中的指定位置 edit.setSelection(1); 需要注 ...
- R 语言基本操作(基本信息的查看、与本地文件系统交互、编译器版本升级)
0. 信息及路径的查看 getwd() ⇒ 查看路径: setwd() ⇒ 设置路径: .libPaths() ⇒ R 语言库所在的位置: sessionInfo() ⇒ 查询版本及系统和库等信息: ...
- 关于ExpandableListView的一个小例子
喜欢显示好友QQ那样的列表,可以展开,可以收起,在android中,以往用的比较多的是listview,虽然可以实现列表的展示,但在某些情况下,我们还是希望用到可以分组并实现收缩的列表,那就要用到an ...
- 基于Web实现在线绘画拓扑图[GraphEditor]
网络拓扑图本来已经整理有一段时间了,一次项目会议写集中边界监控系统的时候上级要求使用可以在系统中画网络拓扑图,没办法当时找不到现有的程序来参考 只能硬着头皮,顶着风险来完成[当然来边界安全的,当然要安 ...
- 微服务实践之路--RPC
微服务实践之路--RPC 重点来了,本文全面阐述一下我们的RPC是怎么实现并如何使用的,跟Kubernetes和Openstack怎么结合. 在选型一文中说到我们选定的RPC框架是Apache Thr ...
- 【剑指offer】直扑克
个大王,2个小王(一副牌原本是54张^_^)...他随机从中抽出了5张牌,想測測自己的手气,看看能不能抽到顺子,假设抽到的话,他决定去买体育彩票,嘿嘿! ! "红心A,黑桃3,小王,大王,方 ...
- EPI_H/EPI_V(边缘保持指数,matlab 矢量化编程)
EPI: edge preservation index,衡量对原始图像的操作(目标图像)对图像边缘的保持能力. EPI_H:horizontal ,水平方向: EPI_V:vertical,垂直方向 ...
- <PC>HP网络共享并创建一个热点问题
郁闷了一年多 你不能使用本地连接的开放热点 网上说的网卡驱动程序,系统设置等.,我们解决不了 原本BIOS节能设置错误 导致在连续主动对自己的有线无线功能被禁用 该LAN\WLAN Switchin ...
- linux_无秘登录问题(不生效)
1 . 登录1,执行命令 ssh-keygen -t rsa 之后一路回 车,查看刚生成的无密码钥对: cd .ssh 后 执行 ll 2 .把 id_rsa.pub 追加到授权的 key 里面去. ...
- 关闭Mac OS 的Rootless
今天在使用mac的时候,需要删除 /usr/bin/下的 自带的php文件.然后提示Operation not permitted 使用sudo 依然不可以,通过google 得到解决方案. 需要关闭 ...