`
bbzjx1983
  • 浏览: 61945 次
  • 性别: Icon_minigender_1
  • 来自: 广州
最近访客 更多访客>>
社区版块
存档分类

axman对equal的深入研究

    博客分类:
  • Java
阅读更多
axman对equal的深入研究

equals方法的重要性毋须多言,只要你想比较的两个对象不愿是同一对象,你就应该实现

equals方法,让对象用你认为相等的条件来进行比较.

 

下面的内容只是API的规范,没有什么太高深的意义,但我之所以最先把它列在这儿,是因为

这些规范在事实中并不是真正能保证得到实现.

 

1.对于任何引用类型, o.equals(o) == true成立.

2.如果 o.equals(o1) == true 成立,那么o1.equals(o)==true也一定要成立.

3.如果 o.equals(o1) == true 成立且o.equals(o2) == true 成立,那么

o1.equals(o2) == true 也成立.

4.如果第一次调用o.equals(o1) == true成立再o和o1没有改变的情况下以后的任何次调用

都成立.

5.o.equals(null) == true 任何时间都不成立.

 

以上几条规则并不是最完整的表述,详细的请参见API文档.

 

对于Object类,它提供了一个最最严密的实现,那就是只有是同一对象是,equals方法才返回true,也就是人们常说的引用比较而不是值比较.这个实现严密得已经没有什么实际的意义,所以在具体子类(相对于Object来说)中,如果我们要进行对象的值比较,就必须实现自己的equals方法.

 

先来看一下以下这段程序:

    public boolean equals(Object obj)    {        if (obj == null) return false;        if (!(obj instanceof FieldPosition))            return false;        FieldPosition other = (FieldPosition) obj;        if (attribute == null) {            if (other.attribute != null) {                return false;            }        }        else if (!attribute.equals(other.attribute)) {            return false;        }        return (beginIndex == other.beginIndex            && endIndex == other.endIndex            && field == other.field);    }

 

这是JDK中java.text.FieldPosition的标准实现,似乎没有什么可说的.

 

我信相大多数或绝大多数程序员认为,这是正确的合法的equals实现.毕竟它是JDK的API实现啊.

 

还是让我们以事实来说话吧:

package debug;import java.text.*;public class Test {  public static void main(String[] args) {    FieldPosition fp = new FieldPosition(10);    FieldPosition fp1 = new MyTest(10);    System.out.println(fp.equals(fp1));    System.out.println(fp1.equals(fp));  }}class MyTest extends FieldPosition{  int x = 10;  public MyTest(int x){    super(x);    this.x = x;  }  public boolean equals(Object o){    if(o==null) return false;    if(!(o instanceof MyTest )) return false;    return ((MyTest)o).x == this.x;  }}

运行一下看看会打印出什么:

 

System.out.println(fp.equals(fp1));打印true

System.out.println(fp1.equals(fp));打印flase

 

两个对象,出现了不对称的equals算法.问题出在哪里(脑筋急转弯:当然出在JDK实现的BUG)?

 

我相信有太多的程序员(除了那些根本不知道实现equals方法的程序员外)在实现equals方法

时都用过instanceof运行符来进行短路优化的,实事求是地说很长一段时间我也这么用过。

太多的教程,文档都给了我们这样的误导。而有些稍有了解的程序员可能知道这样的优化可能

有些不对但找不出问题的关键。另外一种极端是知道这个技术缺陷的骨灰级专家就提议不要这

样应用。

 

我们知道,"通常"要对两个对象进行比较,那么它们"应该"是同一类型。所以首先利用instanceof

运行符进行短路优化,如果被比较的对象不和当前对象是同一类型则不用比较返回false,但事实

上,"子类是父类的一个实例",所以如果子类 o instanceof 父类,始终返回true,这时肯定

不会发生短路优化,下面的比较有可能出现多种情况,一种是不能造型父类而抛出异常,另一种

是父类的private 成员没有被子类继承而不能进行比较,还有就是形成上面这种不对称比较。可能

会出现太多的情况。

 

 

那么,是不是就不能用 instanceof运行符来进行优化?答案是否定的,JDK中仍然有很多实现是正

确的,如果一个class是final的,明知它不可能有子类,为什么不用 instanceof来优化呢?

 

为了维护SUN的开发小组的声誉,我不说明哪个类中,但有一个小组成员在用这个方法优化时在后加

加上了加上了这样的注释:

        if (this == obj)                      // quick check            return true;        if (!(obj instanceof XXXXClass))         // (1) same object?            return false;

可能是有些疑问,但不知道如何做(不知道为什么没有打电话给我......)

 

那么对于非final类,如何进行类型的quick check呢?

 

if(obj.getClass() != XXXClass.class) return false;

 

用被比较对象的class对象和当前对象的class比较,看起来是没有问题,但是,如果这个类的子类

没有重新实现equals方法,那么子类在比较的时候,obj.getClass() 肯定不等于XXXCalss.class,

也就是子类的equals将无效,所以if(obj.getClass() != this.getClass()) return false;才是正

确的比较。

 

 

是否equals方法一定比较的两个对象就一定是要同一类型?上面我用了"通常",这也是绝大多数程序

员的愿望,但是有些特殊的情况,我们可以进行不同类型的比较,这并不违反规范。但这种特殊情况

是非常罕见的,一个不恰当的例子是,Integer类的equals可以和Sort做比较,比较它们的value是不

是同一数学值。(事实上JDK的API中并没有这样做,所以我才说是不恰当的例子)

 

在完成quick check以后,我们就要真正实现你认为的“相等”。对于如果实现对象相等,没有太高

的要求,比如你自己实现的“人”类,你可以认为只要name相同即认为它们是相等的,其它的sex,

ago都可以不考虑。这是不完全实现,但是如果是完全实现,即要求所有的属性都是相同的,那么如

何实现equals方法?

 class Human{private String name;private int ago;private String sex;        ....................        public boolean equals(Object obj){quick check.......Human other = (Human)ojb;return this.name.equals(other.name)&& this.ago == ohter.ago&& this.sex.equals(other.sex);}}

这是一个完全实现,但是,有时equals实现是在父类中实现,而要求被子类继承后equals能正确的工

作,这时你并不事实知道子类到底扩展了哪些属性,所以用上面的方法无法使equals得到完全实现。

一个好的方法是利用反射来对equals进行完全实现:

        public boolean equals(Object obj){quick check.......Class c = this.getClass();Filed[] fds = c.getDeclaredFields();for(Filed f:fds){if(!f.get(this).equals(f.get(obj)))return false;}return true;}

为了说明的方便,上明的实现省略了异常,这样的实现放在父类中,可以保证你的子类的equals可以按

你的愿望正确地工作。

 

关于equals方法的最后一点是:如果你要是自己重写(正确说应该是履盖)了equals方法,那同时就一

定要重写hashCode().为是规范,否则.............

我们还是看一下这个例子:

public final class PhoneNumber {    private final int areaCode;    private final int exchange;    private final int extension;    public PhoneNumber(int areaCode, int exchange, int extension) {        rangeCheck(areaCode, 999, "area code");        rangeCheck(exchange, 99999999, "exchange");        rangeCheck(extension, 9999, "extension");        this.areaCode = areaCode;        this.exchange = exchange;        this.extension = extension;    }    private static void rangeCheck(int arg, int max, String name) {        if(arg < 0 || arg > max)            throw new IllegalArgumentException(name + ": " + arg);    }    public boolean equals(Object o) {        if(o == this)            return true;        if(!(o instanceof PhoneNumber))            return false;        PhoneNumber pn = (PhoneNumber)o;        return pn.extension == extension && pn.exchange == exchange && pn.areaCode == areaCode;    }}

 

注意这个类是final的,所以这个equals实现没有什么问题。

 

我们来测试一下:

    public static void main(String[] args) {        Map hm = new HashMap();        PhoneNumber pn = new PhoneNumber(123, 38942, 230);        hm.put(pn, "I love you");        PhoneNumber pn1 = new PhoneNumber(123, 38942, 230);        System.out.println(pn);        System.out.println("pn.equals(pn1) is " + pn.equals(pn1));        System.out.println(hm.get(pn1));        System.out.println(hm.get(pn));    }

既然pn.equals(pn1),那么我put(pn,"I love you");后,get(pn1)这什么是null呢?

答案是因为它们的hashCode不一样,而hashMap就是以hashCode为主键的。

 

所以规范要求,如果两个对象进行equals比较时如果返回true,那么它们的hashcode要求返回相等的值。

 

原文地址:

http://dev2dev.bea.com.cn/bbs/thread.jspa?forumID=121&threadID=17726&tstart=0

 
分享到:
评论

相关推荐

    axman:Axman MC mod的源代码

    Axman 是一个针对《我的世界》(Minecraft...其开源性质意味着任何感兴趣的人都可以深入研究其代码,学习模组开发,或者对其进行定制以满足个人需求。对于想涉足 Minecraft 模组开发的程序员来说,这是一个很好的起点。

    WebLogic Web Development

    - **axman对equal的深入研究**:讨论了Java中的equals()方法和Object类的默认实现,以及何时应该重写这个方法来实现正确的对象比较。 - **为什么要始终使用PreparedStatement代替Statement**:PreparedStatement...

    web常见问题解答web常见问题解答,可以受到很大的启发

    - **深入研究equal**:Java中的equals()方法是用于比较两个对象是否相等,需要注意与`==`的区别,并且重写equals时通常也要重写hashCode()。 - **使用PreparedStatement代替Statement**:PreparedStatement能防止...

    ServletContext与ServletConfig关系

    &lt;param-value&gt;/usr/mail/ax/axman/Maildir/ 在访问 ServletConfig 对象参数时,可以通过 getInitParameter() 方法获取参数值,而在访问 ServletContext 对象参数时,可以通过 getInitParameter() 方法或 ...

    Java_异步消息处理

    - 将接口(如`Axman`)中的方法调用转化为对象(例如`FutureResult`)。 - 这些对象可以被序列化并通过网络传输到远程服务器。 - 远程服务器上的服务端实现将执行实际的操作并将结果返回。 #### 四、解耦调用...

    Gray Hat Hacking, Second Edition

    Reveal client-side web browser vulnerabilities with MangleMe, AxEnum, and AxMan Probe Windows Access Controls to discover insecure access tokens, security descriptors, DACLs, and ACEs Find and ...

    白帽子讲浏览器安全.钱文祥(带详细书签).pdf

    本书兼顾攻击者、研究者和使用者三个场景,对大部分攻击都提供了分析思路和防御方案。本书从攻击者常用技巧的“表象”深入介绍浏览器的具体实现方式,让你在知其然的情况下也知其所以然。 第1篇 初探浏览器安全 1 1...

    pid控制器代码matlab-CANBus_Elevator:具有多个CAN-Bus节点的电梯系统

    Axman development board with a core HCS12 processor. Notes on compilation for phase 1: - Code warrior ver. 5.1 was used. - Each node needs to be created as a separate project and the common libra

    ServletContext与ServletConfig的深度分析

    &lt;param-value&gt;/usr/mail/ax/axman/Maildir/ ``` 初始化参数可以通过`getInitParameter(String name)`方法获取: ```java String absPath = config.getInitParameter("absPath"); ``` ##### 3.2 获取`...

    编译原理词法分析器

    词法分析是编译过程的第一个阶段,它的主要任务是从左至右逐个字符地对源程序进行扫描,产生一个个单词序列,用以后续的语法分析。该系统采用C++开发,设计并实现C/C++语言词法分析器的基本功能,即读入C/C++语言源程序,...

    RPC简单实现-JAVA

    1. **远程调用原理**:RPC的核心思想是让客户端可以像调用本地方法一样调用远程服务器上的方法,中间过程对用户透明。它通过网络协议将请求发送到远程服务,然后接收返回的结果。 2. **序列化与反序列化**:由于...

    汽车美容管理系统

    ### 汽车美容管理系统知识点解析 #### 一、系统背景与概述 - **系统内容概述**:随着科技的进步和社会的发展,汽车美容行业的市场需求日益增长。传统的手工管理模式已难以适应现代企业管理的需求。...

    学生宿舍管理系统

    学生宿舍管理系统对于一个学校来说是必不可少的组成部分。目前好多学校还停留在宿舍管理人员手工记录数据的最初阶段,手工记录对于规模小的学校来说还勉强可以接受,但对于学生信息量比较庞大,需要记录存档的数据...

    编译原理--词法分析实验(含代码)

    掌握计算机语言的词法分析程序的开发方法。 编制一个能够分析三种整数、标识符、主要运算符和主要关键字的词法分析程序。

    微机原理与接口技术课程设计-交通信号灯

    ### 微机原理与接口技术课程设计-交通信号灯知识点详解 #### 一、项目背景与目标 在《微机原理与接口技术》这门课程的学习过程中,交通信号灯的设计是一个非常典型的应用案例。该案例旨在让学生理解并掌握微处理器...

Global site tag (gtag.js) - Google Analytics