诗剑书生的专栏

我在灌江口上住,花开花落,不知流年度.雁过空遗秋色暮,抚琴细听梧桐雨. 轻舞残虹漫展书,云卷云舒,思愫万千缕.安得婵娟与共处,长作识字耕田夫.                   诗剑书生 于灌江口.听潮居

诗剑书生ID:axman
104011次访问,排名829好友0人,关注者7
一个男人. 一个写程序男人. 一个写程序并从程序中寻找快乐的男人. 一个写程序并从程序中寻找快乐又把快乐传递给大家的男人.
一个书生. 一个寂寞的书生. 一个寂寞的梅香竹影下敲声写韵的书生. 一个寂寞的梅香竹影下敲声写韵晨钟暮鼓中逸气扬剑的书生.
那个男人是位书生。没有人知道他的姓名,居无定所,行无定踪,亦耕亦读,或渔或樵。
axman的文章
原创 87 篇
翻译 0 篇
转载 0 篇
评论 164 篇
axman的公告
最近评论
axman:顺便提一句,开幕式中,绝大多数人都在电视机前,而下面的正常比赛中,绝对大多数人在办公室通过网络访问,是什么样的结果可以想象了.
axman:不用说明什么,一切都如我预料的那样.
在整个奥运开幕式期间,我一边看电视,一边访问
http://www.cctvolympics.com/main.php?type=vod这个地址,其间访问一百多次,没有一次成功.大多数时候是前端缓存的squid在报refuse(111).有时以过几分钟的等待,能出来页面,但Flash缓冲的进度条到了99%就一直停止在那儿.
……
myvicc:写得不错,等写下文
chinagavin:我是先读三位正向输出,然后再读三位反向输出来最终达到想要的结果。
chinagavin:package com;

public class Main {

public static void main(String[] args) {
int[] array = new int[] {1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18};
文章分类
收藏
    相册
    存档
    软件项目交易
    订阅我的博客
    XML聚合  FeedSky
    订阅到鲜果
    订阅到Google
    订阅到抓虾
    订阅到BlogLines
    订阅到Yahoo
    订阅到GouGou
    订阅到飞鸽
    订阅到Rojo
    订阅到newsgator
    订阅到netvibes

    原创 深入equals方法收藏

    新一篇: Spring 调用ORACLE存储过程的结果集 | 旧一篇: 深入 Java 初始化

    深入equals方法

    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;才是正
    确的比较。

    另外一个quick check是if(this==obj) return true;


    是否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要求返回相等的值。

    好了,休息,休息一下。。。。。。。。。。。。。。。。

     

     

    发表于 @ 2004年11月09日 14:48:00|评论(loading...)|编辑

    新一篇: Spring 调用ORACLE存储过程的结果集 | 旧一篇: 深入 Java 初始化

    评论

    #cheung 发表于2004-11-11 16:57:00  IP: 218.106.179.*
    好文章,高手的见解:)
    #cl 发表于2006-11-30 20:27:00  IP: 221.219.10.*
    技术是不错,可文章写得有点令人费解。如果不往后看,你举的MyTest.equals(O)那个例子到底是想说明什么?JDK的bug,那么你是想说明equals实现上的bug,还是关于equals()原则的bug?

    #axman 发表于2006-12-02 09:21:00  IP: 210.82.61.*
    如果每个人都能看懂我的文章,那中国JAVA已经到了令人欣慰的程度了.
    #guiji01 发表于2008-07-18 14:08:30  IP: 219.143.144.*
    哈哈,大概半年前看java的一系列的基础概念时,百思不得其解时,看到了您的文章,确实写的很好,很好。

    虽然现在有些地方仍然一知半解,最近找工作,这两天准备再次拜读。

    ps:n久不来csdn了,专门登陆致谢!真的很希望能认识您。dodoryan#gmail.com
    发表评论  


    当前用户设置只有注册用户才能发表评论。如果你没有登录,请点击登录
    Csdn Blog version 3.1a
    Copyright © axman