免费注册 查看新帖 |

Chinaunix

  平台 论坛 博客 文库
最近访问板块 发新帖
查看: 778 | 回复: 0
打印 上一主题 下一主题

Equals探索(转) [复制链接]

论坛徽章:
0
跳转到指定楼层
1 [收藏(0)] [报告]
发表于 2006-06-09 10:02 |只看该作者 |倒序浏览
转一位牛人的文章。
第一节:eqauls 与 = =之异同
1)比较方式角度:
= =是面向过程的操作符;equals是面向对象的操作符
= =不属于任何类,equals则是任何类(在Java中)的一个方法;
我们可以1)Primitive1 (基本类型)= = Primitive2(基本类型);
         2)Object Reference1(对象引用)= = Object Reference2(对象引用)
         3)Object Reference1 (对象引用) .equals(Object Reference2 (对象引用))
           这三种比较
           但却不能Primitive1 (基本类型).equals( Primitive2(基本类型));
对于基本类型,没有面向对象中发送消息一说,自然也不会有
方法成员。

2)比较目的角度:
1)    如果要比较两个基本类型是否相等,请用= =;
2)    如果要比较两个对象引用是否相等,请用= =;
3)    如果要比较两个对象(逻辑上)是否一致,请用equals;
第二节:对两个对象(逻辑上)是否一致的阐释:
   有人会问:在C++中, 比较两个对象相等不是也可以用==吗?我知道您是指运算符重载,但是很遗憾,Java中不支持运算符重载(java中亦有重载过运算符,他们是“+”, “+=”,不过也仅此两个,而且是内置实现的);所以,对象的是否相等的比较这份责任就交由  equals()来实现 。   
这个“逻辑上”其实就取决于人类的看法,实际开发中,就取决于用户的需求;
有人会有看法:“取决于人类的看法”太过宽泛和不严肃,如果某人要两件
风牛马不相及的事物也相等,equals是否也能作出这样的比较呢?我们说可以的
下面这个例子说明了这一点:

class Horse {
       String Type;
       int Legs;
  //相等的标准:腿的数目相等
       public boolean equals(Object o){
          if(this.Legs==((Cattle)o).Legs){
             return true;
          }
       return false;
}
public Horse(String Type,int legs){
           this.Type=Type;
        this.Legs=legs;
}
}
class Cattle
{
    String Type;
       int  Legs;
     //相等的标准:腿的数目相等
        public Cattle(String Type,int legs){
           this.Type=Type;
        this.Legs=legs;
       }
       public boolean equals(Object o){
          if(this.Legs==((Horse)o).Legs){
             return true;
          }
       return false;
       }
}
public class EqualsTest{
public static void main(String[] args)
       {   
           Cattle c=new Cattle(I'm the Cattle,4);
            Horse h=new Horse(I'm the Horse,4);
           if(c.equals(h)){
                 System.out.println(c.Type);
                 System.out.println(h.Type);
                System.out.println(Cattle Equals Horse);
            }
       }
}
输出结果:I'm the Cattle
          I'm the Horse
          Cattle Equals Horse
您瞧瞧:牛果真等于了马,为何相等?因为我们定义的相等标准是:腿的数目相等;您会说:“这太滑稽”,是滑稽,可这是人类的看法,计算机可没有滑稽的概念,当然也没有“不滑稽”的概念,我们定义了什么相等标准,他就踏踏实实的为我们实现了;
所以说:相等标准(即需求)一定要定好,否则,滑稽的事可就多了
第三节:equals()缘起:
        equals()是每个对象与生俱来的方法,因为所有类的最终基类就是Object(除去Object本身);而equals()是Object的方法之一。
        我们不妨观察一下Object中equals()的source code:
         public boolean equals(Object obj) {
                      return (this == obj);
          }
        注意 “return (this == obj)”
        this与obj都是对象引用,而不是对象本身。所以equals()的缺省实现就是比较
        对象引用是否一致;为何要如此实现呢?前面我们说过:对象是否相等,是由我们的需求决定的,世界上的类千奇百怪(当然,这些类都是我们根据模拟现实世界而创造的),虽然Object是他们共同的祖先,可他又怎能知道他的子孙类比较相等的标准呢?但是他明白,任何一个对象,自己总是等于自己的,何谓“自己总是等于自己”呢,又如何判断“自己总是等于自己”呢?一个对象在内存中只有一份,但他的引用却可以有无穷多个,“对象自己的引用1=对象自己的引用2”,不就能判断“自己总是等于自己”吗?所以缺省实现实现自然也就是
        “return (this == obj)”;
        而到了我们自己编写的类,对象相等的标准由我们确立,于是就不可避免的要覆写
        继承而来的public boolean equals(Object obj);
        如果您有过编覆写过equals()的经验(没有过也不要紧),请您思考一个问题:
         “两个对象(逻辑上)是否一致”实际上是比较什么?没错,或许您已脱口而出:
       就是对象的属性(即field,或称数据成员)的比较。方法是不可比较的哦。(这个问题是不是有些弱智呢?哈哈)
第四节:对一个推论的思考
推论如下:一言以蔽之:欲比较栈中数据是否相等,请用= =;
                      欲比较堆中数据是否相等,请用equals;
因为(根)基本类型,(根)对象引用都在栈中; 而对象本身在堆中;
         这句话又对又不对,问题出在哪,就是“数据”二字,先看栈中,数据或为基本类型,或为对象引用,用==比较当然没错;但是堆中呢?对象不是堆中吗?不是应该用equals比较吗?可是,我们比较的是堆中“数据”,堆中有对象,对象由什么构成呢?可能是对象引用,可能是基本类型,或两者兼而有之。如果我们要比较他们,该用什么呢,用”equals()”?不对吧,只能是”= =”!所以正确的结论是:欲比较栈中数据是否相等,请用= =;欲比较堆中数据是否相等,请用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  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要求返回相等的值。
好了,休息,休息一下。。。。。。。。。。。。。。。。
Equals方法和==运算的区别是一个比较重要的课题。引用类型缺省的Equals方法实现在System.Object类型中。在《.NET 框架程序设计(修订版)》中列出了System.Object类的Equals方法实现方法:
class Object
{
    public virtual Boolean Equals(Object obj)
    {
        //如果两个引用指向的是同一个对象,
        //它们肯定相等。
        if (this == obj) return true;
        //假定两个对象不相等。
        return false;
    }
}
从上面的代码中可以看到,在System.Object类的Equals方法返回的值和==运算是一致的。于是专门做了个C#的试验研究了一下。
class CBase
{
    public int i;
    public CBase(int n)
    {
        i = n;
    }
}
class CDerive : CBase
{
    public CDerive(int n) : base(n)
    {
    }
}
class SBase
{
    public int i;
    public SBase(int n)
    {
        i = n;
    }
}
class SDerive : SBase
{
    public SDerive(int n) : base(n)
    {
    }
}
class TestMain
{
    [STAThread]
    static void Main(string[] args)
    {
        //两个相同值的整型。
        int i = 5;
        int j = 5;
        Console.WriteLine((i == j) + " " + i.Equals(j));//Ture True
        //相同值的整型和浮点型。
        float f = 5;
        Console.WriteLine((i == f) + " " + i.Equals(f));//Ture False
        //两个相同的对象。
        CDerive a = new CDerive(4);
        CDerive b = new CDerive(4);
        Console.WriteLine((a == b) + " " + a.Equals(b));//False False
        //指向同一对象的不同变量。
        CDerive c = a;
        Console.WriteLine((a == c) + " " + a.Equals(c));//True True
        //指向同一对象的不同类型的变量。
        CBase d = a;
        Console.WriteLine((a == d) + " " + a.Equals(d));//True True
        //两个相同的结构。
        SDerive A = new SDerive(4);
        SDerive B = new SDerive(4);
        Console.WriteLine((A == B) + " " + A.Equals(B));//False False
        //指向同一结构的不同变量。
        SDerive C = A;
        Console.WriteLine((A == C) + " " + A.Equals(C));//True True
        //指向同一结构的不同类型的变量。
        SBase D = A;
        Console.WriteLine((A == D) + " " + A.Equals(D));//True True
        //装箱为Object的基元变量。
        object s = i;
        object t = j;
        Console.WriteLine((s == t) + " " + s.Equals(t));//False True
        //装箱为ValueType的基元变量。
        ValueType u = i;
        ValueType v = j;
        Console.WriteLine((u == v) + " " + u.Equals(v));//False True
    }
}
比较之下,缺省实现的Equals方法和==运算符并不一定完全返回相同的值。不过仔细辨别以后就可以发现上面的例子中并不都执行 System.Object类的Equals方法。比如“i.Equals(f)”中,由于i是整行变量,因此这里执行的Equals方法是 System.Int32的Equals方法;在“s.Equals(t)”中,虽然s是System.Objecto类型的变量,但它的内容却是 System.Int32类型的,因此执行的也是System.Int32的Equals方法;最后个例子的“u.Equals(v)”和 “s.Equals(t)”一样,执行的仍然是“s.Equals(t)”方法,而==运算则仍然是System.Object类的,故此出现了 Equals方法和==运算结果不同的情况。
从这些实例中我们可以总结出Equals方法和==运算符究竟有什么不同,以便在重写Equals方法和==运算符时有所借鉴。
一般情况下,Equals方法着重于比较两个变量所指对象的类型和值,但并不要求这两个变量必须是引用同一对象。“i.Equals(f)”的值之所以是 False,就是因为变量i和变量f的类型是不同的,前者是整型而后者是浮点型。“a.Equals(b)”和“A.Equals(B)”的值应该是 True,因为a和b与A和B虽然不是同一对象,但值是相同的,之所以结果是False,是因为它们调用的是System.Object类和 System.ValueType类的Equals方法,因此需要我们重写Equals方法使“a.Equals(b)”和“A.Equals(B)”反回的结果为True。
对于引用类型,==运算只有在比较的双方引用的是同一对象时才会返回True,因此一般情况下不用重写==运算。对于值类型,只要在概念上存在相等关系,==运算就会返回True,即使这两个对象的类型不同。s == t和u == v值为False说明在同样的值类型的变量在装箱后产生的引用对象是不同的。i == f的值为True则说明对于值类型,==运算只求概念上相等,不比较方法的类型。


本文来自ChinaUnix博客,如果查看原文请点:http://blog.chinaunix.net/u/16357/showart_125544.html
您需要登录后才可以回帖 登录 | 注册

本版积分规则 发表回复

  

北京盛拓优讯信息技术有限公司. 版权所有 京ICP备16024965号-6 北京市公安局海淀分局网监中心备案编号:11010802020122 niuxiaotong@pcpop.com 17352615567
未成年举报专区
中国互联网协会会员  联系我们:huangweiwei@itpub.net
感谢所有关心和支持过ChinaUnix的朋友们 转载本站内容请注明原作者名及出处

清除 Cookies - ChinaUnix - Archiver - WAP - TOP