`
Mysun
  • 浏览: 273131 次
  • 性别: Icon_minigender_1
  • 来自: 杭州
社区版块
存档分类
最新评论

Java语言深入:深入研究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要求返回相等的值。
分享到:
评论

相关推荐

    东北大学 研究生高级Java语言试题2017

    在深入分析东北大学2017年研究生高级Java语言试题之前,首先需要指出,由于提供的文档部分包含了扫描文本中的错误识别,本回答将对内容进行修正并补充完整知识点。 一、名词解释 1. J2SE:Java 2 Platform Standard...

    261个Java语言问题

    Java语言是现代软件开发的重要工具,它以其面向对象特性、跨平台能力以及强大的库支持而闻名。本章将深入探讨Java的基本概念、环境配置以及编程基础。...全面掌握Java语言,需要不断实践和深入研究。

    Java API_5.0中文版

    以上只是Java API 5.0中的一部分核心知识点,实际的学习过程中,还需要深入研究每个类和接口的功能,熟悉它们的API用法,并通过实践来加深理解。阅读Java API文档是提升编程技能和解决问题的重要途径。

    对Java中Set的深入研究

    ### 对Java中Set的深入研究 #### 一、引言 在Java编程语言中,`Set`接口是一种非常重要的集合类型,它代表了一个无序且不允许包含重复元素的集合。`Set`接口属于Java集合框架的一部分,继承自`Collection`接口,并...

    Java中Set的深入研究

    在深入探讨Java中Set接口的实现细节之前,我们先来明确一下Set在Java中的核心概念。Set接口是Java集合框架的一部分,它代表了一个数学抽象集合,即不允许包含重复元素的集合。更正式地讲,根据其Javadoc文档,Set是...

    静态的魔力:Java中静态方法和变量的深度解析

    - **Java内存模型**:深入研究Java内存模型,了解静态方法和变量是如何存储和访问的。 通过不断学习和实践,你将能够更深入地掌握Java中静态方法和变量的使用技巧,成为一名更高效的Java开发者。

    javaapi中文版

    这些只是Java API中的一部分关键知识点,实际的Java API文档包含了更广泛的内容,包括更多的类、接口和方法,每个都值得深入研究和实践。通过阅读和理解“javaapi中文版”,开发者可以更好地掌握Java编程,提升编程...

    JAVA经典教材笔记

    - Java的发展历程:从1995年由Sun Microsystems公司发布以来,Java经历了多次重大版本更新,成为当今最流行的编程语言之一。 - Java的主要版本介绍:包括Java SE(Standard Edition)、Java EE(Enterprise ...

    对Java中Set的深入研究.pdf

    以下是对Java Set实现的一些深入讨论: 1. **Set的实现类**: - `AbstractSet`:一个抽象类,实现了Set接口的部分方法,为其他Set实现提供基础。 - `CopyOnWriteArraySet`:线程安全的Set实现,内部基于数组,当...

    Java rt.jar源码

    总的来说,深入研究Java rt.jar源码对于Java开发者来说是一项重要的学习任务。通过理解rt.jar中的源码,我们可以更深入地了解Java平台的工作原理,提高编程技巧,解决实际问题,同时也能为设计和实现高效、可靠的...

    java api源代码

    通过深入研究Java API源代码,开发者不仅可以增强对Java语言特性的理解,还能提升调试和性能优化的能力。这对于成为一个真正的Java高手来说是必不可少的步骤。同时,这也是一个持续学习和探索的过程,因为Java API...

    java API 帮助文档

    Java API(应用程序接口)是Java开发的核心组成部分,它提供了一系列预先定义好的类和方法,使得开发者可以方便地进行系统级编程。...通过深入研究这份文档,开发者可以更好地掌握Java编程的精髓,提高代码质量和效率。

    猜拳游戏:java面向对象,kotlin面向对象,js面向对象,3个方法开发.zip

    标题 "猜拳游戏:java面向对象,kotlin面向对象,js面向对象,3个方法开发.zip" 提供了关于一个编程项目的概览,这个项目采用了三种不同的编程语言——Java、Kotlin和JavaScript,来实现同一个猜拳游戏。面向对象...

    运用 java 语言 实现 TCP/udp 聊天程序

    本教程将深入讲解如何运用Java语言实现TCP和UDP聊天程序。 首先,TCP(Transmission Control Protocol)是一种面向连接的、可靠的、基于字节流的传输层通信协议。它通过三次握手建立连接,确保数据的有序、无丢失...

    JAVA复习提纲(用于Java复习考试)

    Java是一种广泛使用的面向对象的编程语言,其设计目标是具有高度的可移植性、健壮性和安全性。在Java的学习和复习过程中,以下是一些关键的知识点: ...继续深入研究,将有助于成为更专业的Java开发者。

    java实现的语法分析程序

    通过深入研究这些源代码,我们可以了解到如何将理论概念转化为实际的Java代码,以及如何处理错误、优化性能等方面的知识。 总之,Java实现的语法分析程序是一项涉及到编程语言理论和实践的技术,它需要对词法分析和...

    java常用API-适合初学者

    Java API,全称为Java应用程序接口,是Java编程语言的核心...随着经验的积累,可以深入研究更多的高级特性和框架,提升自己的编程技能。记得实践是检验真理的唯一标准,多写代码,多思考,才能真正掌握Java API的精髓。

    Java基础面试题,适合1-2年工作经验的小伙伴

    Java是企业级应用开发的重要语言,对于1-2年经验的开发者来说,深入理解Java基础知识以及相关技术是非常必要的。以下是一些关键知识点的详细说明: ...对于每个知识点,深入研究和实践都是非常有价值的。

    platzi-java-basico:Platzi Java SE入门课程

    - String类:研究String类的常用方法,如concat, substring, equals, length, replace等。 - StringBuilder与StringBuffer:了解这两个类在字符串操作中的优势,特别是在性能上的考虑。 10. **集合框架**: - ...

    java优秀源码-Effective-Java-Concepts:Java源代码可以强化概念,我正在从JoshuaBloch的出色著作“Eff

    通过对书中源代码的分析,我们可以更深入地理解Java语言的特性和最佳实践。 在阅读和研究《Effective Java》中的源码时,我们可以关注以下几个关键知识点: 1. **单例模式**:书中的Item 3 "考虑单例类" 提到了...

Global site tag (gtag.js) - Google Analytics