经过对四种不同类型判等方法的讨论,我们不难发现不管是 Equals 静态方法、Equals 虚方法 抑或==操作符的执行结果,都可能受到覆写 Equals 方法的影响。因此研究对象判等就必须将注意 力集中在自定义类型中如何实现 Equals 方法,以及实现怎样的 Equals 方法。因为,不同的类型, 对于“相等”的理解会有所偏差,你甚至可以在自定义类型中实现一个总是相等的类型,例如:

class AlwaysEquals
{
public override bool Equals(object obj)
{
return true;
}
}

  因此,Euqls 方法的执行结果取决于自定义类型的具体实现规则,而.NET 又为什么提供这种机 制来实现对象判等策略呢?首先,对象判等决定于需求,没有必要为所有.NET 类型完成逻辑判等, System.Object 基类也无法提供满足各种需求的判等方法;其次,对象判等包括值判等和引用判等 两个方面,不同的类型对判等的处理又有所不同,通过多态机制在派生类中处理各自的判等实现 显然是更加明智与可取的选择。

  接下来,我们开始研究如何通过覆写 Equals 方法实现对象的判等。覆写 Equals 往往并非易事, 要综合考虑到对值类型字段和引用类型字段的分别判等处理,同时还要兼顾父类覆写所带来的影 响。不适当的覆写会引发意想不到的问题,所以必须遵循三个等价原则:自反、传递和对称,这 是实现 Equals 的通用契约。那么又如何为自定义类型实现 Equals 方法呢?

  最好的参考资源当然来自于.NET 框架类库的实现,事实上,关于 Equals 的覆写在.NET 中已经 有很多的基本类型完成了这一实现。从值类型和引用类型两个角度来看:

  对于值类型,基类 System.ValueType 通过反射机制覆写了 Equals 方法来比较两个对象的值相 等,但是这种方式并不高效,更明智的办法是在自定义值类型时有针对性的覆写 Equals 方法, 来提供更灵活、高效的处理机制。

  对于引用类型,覆写 Equals 方法意味着要改变 System.Object 类型提供的引用相等语义。那么, 覆写 Equals 要根据类型本身的特点来实现,在.NET 框架类库中就有很多典型的引用类型实现 了值相等语义。例如 System.String 类型的两个变量相等意味着其包含了相等的内容,System. Version 类型的两个变量相等也意味着其 Version 信息的各个指标分别相等。

  因此对 Equals 方法的覆写主要包括对值类型的覆写和对引用类型的覆写,同时也要区别基类 是否已经有过覆写和不曾覆写两种情况,并以等价原则为前提,进行判断。在此,我们仅提供较 为标准的实现方法,具体的实现取决于不同的类型定义和语义需求。

    class EqualsEx
{
//定义值类型成员 ms
private MyStruct ms;
//定义引用类型成员 mc
private MyClass mc;
public override bool Equals(object obj)
{
//为 null,则必不相等
if (obj == null) return false;
//引用判等为真,则二者必定相等
if (ReferenceEquals(this, obj)) return true;
//类型判断
EqualsEx objEx = obj as EqualsEx;
if (objEx == null) return false;
//最后是成员判断,分值类型成员和引用类型成员
//通常可以提供强类型的判等方法来单独处理对各个成员的判等
return EqualsHelper(this, objEx);
}
private static bool EqualsHelper(EqualsEx objA, EqualsEx objB)
{
//值类型成员判断
if (!objA.ms.Equals(objA.ms)) return false;
//引用类型成员判断
if (!Equals(objA.mc, objB.mc)) return false;
//最后,才可以判定两个对象是相等的
return true;
}
} internal struct MyStruct
{
} internal class MyClass
{
}

  上述示例只是从标准化的角度来阐释 Equals 覆写的简单实现,而实际应用时又会有所不同, 然而总结起来实现 Equals 方法我们应该着力于以下几点:首先,检测 obj 是否为 null,如果是则 必然不相等;然后,以 ReferenceEquals 来判等是否引用相等,这种办法比较高效,因为引用相等 即可以推出值相等;然后,再进行类型判断,不同类型的对象一定不相等;最后,也是最复杂的 一个过程,即对对象的各个成员进行比较,引用类型进行恒定性判断,值类型进行恒等性判断。 在本例中我们将成员判断封装为一个专门的处理方法 EqualsHelper,以隔离对类成员的判断实现, 主要有以下几个好处:

  符合 Extract Method 原则,以隔离相对变化的操作。

  提供了强类型版本的 Equals 实现,对于值类型成员来说还可以避免不必要的装箱操作。

  为==操作符提供了重载实现的安全版本。

在.NET 框架中,System.String 类型的 Equals 覆写方法就提供了 EqualsHelper 方法来实现。

8.2.3 覆写 Equals的更多相关文章

  1. [改善Java代码]覆写equals方法必须覆写hashCode方法

    覆写equals方法必须覆写hashCode方法,这条规则基本上每个Javaer都知道,这也是JDK API上反复说明的,不过为什么要这样做呢?这两个方法之间有什么关系呢?本建议就来解释该问题,我们先 ...

  2. [改善Java代码]覆写equals方法时不要识别不出自己

    建议45: 覆写equals方法时不要识别不出自己 我们在写一个JavaBean时,经常会覆写equals方法,其目的是根据业务规则判断两个对象是否相等,比如我们写一个Person类,然后根据姓名判断 ...

  3. java覆写equals方法

    何时需要重写equals() 当一个类有自己特有的“逻辑相等”概念(不同于对象身份的概念). object规范规定,如果要重写equals(),也要重写hashcode() 如何覆写equals() ...

  4. 为什么覆写equals()方法的时候总是要覆写hashcode()?

    要回答这个问题,我们应该先认识一下obj中的equals和hascode方法 1.equals()方法在obj中定义如下: public boolean equals(Object obj) { re ...

  5. 为什么覆写equals必须要覆写hashCode?

    ============================================= 原文链接: 为什么覆写equals必须要覆写hashCode? 转载请注明出处! ============= ...

  6. 覆写equals方法为什么需要覆写hashCode方法

    覆写equals方法必须覆写hashCode方法,是JDK API上反复说明的,不过为什么要这样做呢?这两个方法之间有什么关系呢? void test() { // Person类的实例作为Map的k ...

  7. (强制)要求覆写equals必须覆写hashCode(原理分析)

    hashCode和equals hashCode和equals用来标识对象,两个方法协同工作可用来判断两个对象是否相等.众所周知,根据生成的哈希将数据散列开来,可以使存取元素更快.对象通过调用Obje ...

  8. 11.JAVA-Object类之finalize(),clone(),toString()等方法覆写

    1.Object介绍 Object类是一个特殊的类,是所有类(包括数组,接口 )的父类,如果一个类没有用extends明确指出继承于某个类,那么它默认继承Object类,所以可以通过向上转型的方法使用 ...

  9. object 类 toString() 和 equals() 的覆写

    基本作用: objiect类是所有类的父类. 任何一个类定义的时候如果没有明确定义了父类的话,默认父类是Object类. class A extends Object{} 在整个java里面,类的继承 ...

随机推荐

  1. javascript 中文与Unicode相互转化

      javascript 中文与Unicode相互转化 CreateTime--2018年3月30日11:26:50 Author:Marydon /** * 中文与Unicode的相互转换 */ v ...

  2. java线程和线程池的使用

    java线程和线程池 一.创建多线程的方式 java多线程非经常见.怎样使用多线程,怎样创建线程.java中有两种方式,第一种是让自己的类实现Runnable接口.另外一种是让自己的类继承Thread ...

  3. SpringBoot之Web开发——webjars&静态资源映射规则

    在webjars中找到需要引入的Maven依赖,添加到pom.xml中,即可自动导入相关依赖.

  4. Batch基本知识

    一般情况下,每条命令占据一行: 当然也可以将多条命令用特定符号(如:&:.&&:.|.||等)分隔后写入同一行中: 还有的情况就是像if.for等较高级的命令则要占据几行.几十 ...

  5. Linux gadget驱动分析2------设备识别过程

    设备连上主机之后,设备驱动做了的事. 设备连上host的port之后,主机端会有一套策略发送请求获取device的一系列描述符.进行枚举过程.找到适合该device的驱动. 这样就可以与device进 ...

  6. bzoj 4590: [Shoi2015]自动刷题机

    好恶心.. 二分上界到100000LL*1000000000LL  %_% #include<cstdio> #include<iostream> #include<cs ...

  7. MFC基础学习

    RECT rect = { }; //获取窗口的内部客户区矩形 GetClientRect(&rect); 模态和费模态对话框! 模态对话框只需要包含对话框头文件,定义对话框类,调用DoMod ...

  8. codeforces 939E Maximize! 双指针(two pointers)

    E. Maximize! time limit per test 3 seconds memory limit per test 256 megabytes input standard input ...

  9. jsp jquery js 的基本路径获取

    引子:js中需要当前页面的基础路径,获取不到request,可以通过如下方法来解决!   1.jsp基础路径,在jsp头部加上,获取基础路径http://localhost:8080/project/ ...

  10. python自动化测试学习笔记-6redis应用

    上次我们学到了redis的一些操作,下面来实际运用以下. 这里我们先来学习一下什么是cookie和session. 什么是Cookie 其实简单的说就是当用户通过http协议访问一个服务器的时候,这个 ...