zoukankan      html  css  js  c++  java
  • 对象判等

    一直都认为对于对象判等自己明白了,可是真当与别人深入交流时,茫然了~~~痛定思痛,花了足足三个小时,整理了一番。要想深入理解这块知识,必须对CLR内存管理机制有一定的了解。废话不多说。

    一、首先牢记两个基本概念:

    (1)值相等:表示两个对象的数据成员按内存位分别相等,即两个对象类型相等,并且具有相等和相同的字段。
    (2)引用相等:表示两个引用指向同一对象实例,也就是同一内存地址,因此可以由引用相等推出其值相等,反之则不然。

    二、本质分析:

    .NET对于对象判等总共有四个方法,虚拟的Equals()方法,静态的Equals()方法,静态的ReferenceEquals()方法,==操作符。现一一分析。

    (1)Equals()虚方法:用于比较两个类型实例是否相等,也就是判断两个对象是否具有相同的“值”代码实现可以“表示”为

    public virtual bool Equals(object obj)
    {
         return InernalEquals(this,obj);
    }
    //其中InternalEquals可以表示为
    if(this==obj)
    {
         return true;
    }
    else
    {
         return false;
    }

    可见默认情况下,Equals方法和referenceEquals方法是一样的,object的equals虚方法仅仅提供了最最简单的比较策略:如果两个引用指向同一对象,true,否则false。也就是判断是否引用相等。然而这种方法并未达到Equals比较两个对象值相等的目标,所以System.Object将这个任务交个其派生对象去重新实现,可以说Equals的比较结果取决于类的创建者是如何实现的,而非统一性约定。事实上,框架类中很多引用类型的Equals方法用于比较值相等,例如最典型的String类型对象是否相等,肯定关注起内容是否相等,判断的是值相等语义。

    (2)Equals()静态方法:实现了对两个对象的相等性判别,其在System.Object类型中实现过程可以"表示"为(而不是说其在Object类中就是这样实现的)

    public static bool Equals(object objA, object objB)
    {
        if (objA == objB)
        {
            return true;
        }
        if ((objA != null) && (objB != null))
        {
            return objA.Equals(objB);
        }
        return false;
    }

    所有Equals()静态方法的执行结果依次取决于三个条件
    ①是否为同一实例
    ②是否都为null
    ③第一个参数的Equals()实现

    故通常情况下Equals静态方法的执行结果常常受到“判等对象”的影响,如下测试。

    namespace 类型判等
    {
    class Program
    {
    static void Main(string[] args)
    {
    MyClassA classA
    = new MyClassA();
    MyClassB classB
    = new MyClassB();
    Console.WriteLine(Equals(classA,classB));
    //true,实际上执行的是classA.Equals(classB);返回true
    Console.WriteLine(Equals(classB, classA));//false,实际上执行的是classB.Equals(classA);返回false
    Console.ReadLine();
    }
    }
    class MyClassA
    {
    public override bool Equals(object obj)
    {
    return true;
    }
    }
    class MyClassB
    {
    public override bool Equals(object obj)
    {
    return false;
    }
    }
    }
    执行结果为:
    Image00011

    由执行结果知道,静态Equals方法的执行取决于==操作符,和Equals虚方法这两个因素,因此决议静态Equals方法的执行,就要在

    自定义类型中重写虚拟的Equals方法和重载==操作符。还有静态Equals方法可以解决两个值为null的对象的判等问题,而是用objA.Equals(objB)来判断两个null对象会抛出NullReferenceException异常。

    (3)静态ReferenceEquals()方法,因为ReferenceEquals为静态方法,所以不能重写该方法,只能使用System.Object中的实现代码,具体为
            public static bool ReferenceEquals(object objA, object objB)
            {
                return (objA == objB);
            }

    如下示例:

    namespace 类型判等
    {
    class Program
    {
    static void Main(string[] args)
    {
    MyClass classA
    = new MyClass();
    MyClass classB
    = new MyClass();
    //classA,classC指向同一对象实例
    MyClass classC = classA;

    Console.WriteLine(ReferenceEquals(classA,classB));
    //false
    Console.WriteLine(ReferenceEquals(classA, classC));//true
    Console.WriteLine(ReferenceEquals(null, null));//true
    Console.WriteLine(ReferenceEquals(classA, null));//false
    Console.ReadLine();
    }
    }
    class MyClass
    {
    }
    }

    结果:

    Image00012
    可见ReferenceEquals方法用于判断两个引用是否指向同一对象,也就强调的引用相等,因此ReferenceEquals比较同一类型的两个对象实例将范虎false而.NET认为null等于null。

    (4)“==”操作符

    值类型下:表示是否值相等,由值类型的根类System.ValueType提供了实现,
    引用类型下:表示是否“引用相等”即两个引用指向同一个对象实例。牢记此话。

    当然也有例外 还是string ==表示的是值相等,而非引用相等。

    三、实际应用

    值类型判等

    ①Equals,System.Valuetype重载了System.Object的Equals方法,用于实现对实例数据的判等。

    ②ReferenceEquals 永远返回false说明:用ReferenceEquals()方法比较两个值类型变量毫无意义,结果肯定是false,即使是2个值相等的变量,因为在ReferenceEquals()方法比较前,被比较的值类型变量将会被装箱操作,隐性地创建两个不同对象,所以其地址引用也将不同。

    ③== 为重载的==的值类型,将比较两个值是否“按位”相等(不懂,只知道是比较两个值是否相等,还请高人指点!)

    引用类型判等

    ①静态的ReferenceEquals:两个实例对象是否指向同一引用地址。

    ②静态的Equals:
         是否为同一实例
         是否都为null
         第一个参数的Equals()实现

    ③虚拟的Equals默认为引用地址比较。

    ④== 默认为引用地址比较

    四、小结——重写Equals()方法 1、经过对四种不同类型判等方法的讨论,我发现不管是Equals静态方法,Equals虚拟方法,==操作符的执行结果,都可能受到重写Equals方法的影响,所以在对象判等时就必须注意自定义类型中如何实现Equals方法,以及实现怎么样的Equals方法,不同的类型,“相等”会有偏差。

    2、因此Equals方法的执行结果往往取决于自定义类型的具体实现规则,而为什么.NET提供这种机制

    (1)对象判等取决于需求,没必要为所有.NET类型完成逻辑判等,System.Object也无法满足各种需求的判等方法。

    (2)不同类型的判等的处理不同,通过多态机制在派生类中处理各自的判等实现是明智的。

    3、重写Equals要综合考虑值类型,引用类型的判等,同时要兼顾父类所带来的影响,另外遵循三个原则,自发,传递,对称,还要注意重写GetHashCode()方法。

    另外关于对象判等还得注意String类型的字符串驻留机制导致的String判等的特殊性。

    题外话:本贴主要参考园子里王涛老师写的那本《你必须知道的.NET》一书,推荐大家都去看看此书,绝对值得一看。另有不当之处,还望各位指正。

  • 相关阅读:
    LeetCode 226. Invert Binary Tree
    LeetCode 221. Maximal Square
    LeetCode 217. Contains Duplicate
    LeetCode 206. Reverse Linked List
    LeetCode 213. House Robber II
    LeetCode 198. House Robber
    LeetCode 188. Best Time to Buy and Sell Stock IV (stock problem)
    LeetCode 171. Excel Sheet Column Number
    LeetCode 169. Majority Element
    运维工程师常见面试题
  • 原文地址:https://www.cnblogs.com/liujb/p/2069978.html
Copyright © 2011-2022 走看看