【十八掌●基本功篇】第一掌:Java之String的equals方法

这一篇博文是【大数据技术●降龙十八掌】系列文章的其中一篇,点击查看目录:这里写图片描述大数据技术●降龙十八掌


1、Java的一个例子
public static void main(String[] arge) {

        //1
        String str1 = new String("1234");
        String str2 = new String("1234");
        System.out.println("new String()==:" + (str1 == str2));
        //String为引用类型,str1和str2为新实例化出来的对象,分别指向不同的内存地址。而==对于引用类型判断,是判断的是引用地址,所以例子1结果为false。

        //2
        String str3 = "1234";
        String str4 = "1234";
        System.out.println("常量字符串==:" + (str3 == str4));
        //对于第二个例子,编译器编译代码时,会将"1234"当做一个常量,并保存在JVM的常量池中,然后编译String str3="1234";时,将常量的指针赋值给str3,在编译String str4="1234";时,编译器查找常量池里有相同的常量,就将存在的常量指针赋给str4,这样结果就是str3和str4都指向了常量池中的常量的地址,所以==比较结果为true;

        //3
        String str5 = "1234";
        String str6 = "12" + "34";
        System.out.println("常量表达式==:" + (str5 == str6));
        //第三个例子,编译时编译器发现能够计算出"12"+"34"的值,它是个常量表达式,就按照第二个例子一样处理,最终str5和str6都指向了同一个内存地址。所以==比较结果为true;

        //4
        String str7 = "1234";
        String str8 = "12" + 34;
        System.out.println("字符串和数字相加的表达式==:" + (str7 == str8));
        //第四个例子、第五个例子和第六个例子,类似第三个例子,编译时编译器发现是常量表达式,能够计算出值,就尽量计算出来,所以==比较结果为true;

        //5
        String str9 = "12true";
        String str10 = "12" + true;
        System.out.println("字符串和Boolen相加表达式==:" + (str9 == str10));

        //6
        final String val = "34";
        String str11 = "1234";
        String str12 = "12" + val;
        System.out.println("字符串和常量相加的表达式==:" + (str11 == str12));

        //7
        String str13 = "1234";
        String str14 = "12" + getVal();
        System.out.println("字符串和函数得来的常量相加表达式==:" + (str13 == str14));
        //第七个例子中,编译器发现str14值是要调用函数才能计算出来的,是要在运行时才能确定结果的,所以编译器就设置为运行时执行到String str14="12" + getVal();时 要重新分配内存空间,导致str13和str1是指向两个不同的内存地址,所以==比较结果为false;
    }
    private static String getVal()
    {
        return "34";
    }

运行输出:    

new String()==:false
常量字符串==:true
常量表达式==:true
字符串和数字相加的表达式==:true
字符串和Boolen相加表达式==:true
字符串和常量相加的表达式==:true
字符串和函数得来的常量相加表达式==:false

2、Java中的equals总结

(1) String是引用类型;==是关系运算符,==比较两个引用类型时,判断的依据是:双方是否是指向了同一个内存地址。Java中的==,就是个关系运算符,始终遵守着它自己的规则,即:值类型比较久比较值,引用类型比较就比较内存地址。

(2) String类中的equals()方法是重写了Object类的equals()方法,始终是比较两个字符串是否一样。

(3) 在编译器编译代码时,指定的字符串是个常量表达式,String类型的是引用类型,编译器先将常量表达式的内存地址赋值给一个常量,之后用到同样的字符串时,首先查看当前作用域中是否存在了同值的常量,如果存在就使用这个常量,导致指向了同一个内存地址。

3、C#下的一个例子

static void Main(string[] args)
{
    //1
    String str1 = new String(new char[] { '1', '2', '3', '4', });
    String str2 = new String(new char[] { '1', '2', '3', '4', });
    Console.WriteLine("①new String()方式下==:" + (str1 == str2));            
    Console.WriteLine("②new String()方式下equals:" + str1.Equals(str2));
    //C#里重写了==运算符,使得==比较的不再是引用,而是值,所以==和Equals方法是完全一致的了
    //例子1中结果都是True

    //2
    String str3 = "1234";
    String str4 = "1234";
    Console.WriteLine("③赋值常量方式下==:" + (str3 == str4));
    Console.WriteLine("④赋值常量方式下equals:" + str3.Equals(str4));
    //结果也是True

    //3
    String val = "1234";
    String str5 = val;
    String str6 = val;
    Console.WriteLine("⑤赋值变量方式下==:" + (str5 == str6));
    Console.WriteLine("⑥赋值变量方式下equals:" + str5.Equals(str6));
    //结果也是True

    Console.ReadLine();
}

运行输出:

①new String()方式下==:True
②new String()方式下equals:True
③赋值常量方式下==:True
④赋值常量方式下equals:True
⑤赋值变量方式下==:True
⑥赋值变量方式下equals:True

5、代码分析

(0) String类的Equals方法的实现,是实现对字符串是否相同的比较。

public override bool Equals(object obj)
{
    if (this == null)
    {
        throw new NullReferenceException();
    }
    string strB = obj as string;
    if (strB == null)
    {
        return false;
    }
    if (this == obj)
    {
        return true;
    }
    if (this.Length != strB.Length)
    {
        return false;
    }
    return EqualsHelper(this, strB);
}

private static unsafe bool EqualsHelper(string strA, string strB)
{
    int length = strA.Length;
    fixed (char* chRef = &strA.m_firstChar)
    {
        fixed (char* chRef2 = &strB.m_firstChar)
        {
            char* chPtr = chRef;
            char* chPtr2 = chRef2;
            while (length >= 10)
            {
                if (*(((int*) chPtr)) != *(((int*) chPtr2)))
                {
                    return false;
                }
                if (*(((int*) (chPtr + 2))) != *(((int*) (chPtr2 + 2))))
                {
                    return false;
                }
                if (*(((int*) (chPtr + 4))) != *(((int*) (chPtr2 + 4))))
                {
                    return false;
                }
                if (*(((int*) (chPtr + 6))) != *(((int*) (chPtr2 + 6))))
                {
                    return false;
                }
                if (*(((int*) (chPtr + 8))) != *(((int*) (chPtr2 + 8))))
                {
                    return false;
                }
                chPtr += 10;
                chPtr2 += 10;
                length -= 10;
            }
            while (length > 0)
            {
                if (*(((int*) chPtr)) != *(((int*) chPtr2)))
                {
                    break;
                }
                chPtr += 2;
                chPtr2 += 2;
                length -= 2;
            }
            return (length <= 0);
        }
    }
}

(1)String类中对==关系运算符进行了重写,也是实现了对字符串是否相同的比较。

public static bool operator ==(string a, string b)
{
    return Equals(a, b);
}

public static bool Equals(string a, string b)
{
    if (a == b)
    {
        return true;
    }
    if ((a == null) || (b == null))
    {
        return false;
    }
    if (a.Length != b.Length)
    {
        return false;
    }
    return EqualsHelper(a, b);
}

private static unsafe bool EqualsHelper(string strA, string strB)
{
    int length = strA.Length;
    fixed (char* chRef = &strA.m_firstChar)
    {
        fixed (char* chRef2 = &strB.m_firstChar)
        {
            char* chPtr = chRef;
            char* chPtr2 = chRef2;
            while (length >= 10)
            {
                if (*(((int*) chPtr)) != *(((int*) chPtr2)))
                {
                    return false;
                }
                if (*(((int*) (chPtr + 2))) != *(((int*) (chPtr2 + 2))))
                {
                    return false;
                }
                if (*(((int*) (chPtr + 4))) != *(((int*) (chPtr2 + 4))))
                {
                    return false;
                }
                if (*(((int*) (chPtr + 6))) != *(((int*) (chPtr2 + 6))))
                {
                    return false;
                }
                if (*(((int*) (chPtr + 8))) != *(((int*) (chPtr2 + 8))))
                {
                    return false;
                }
                chPtr += 10;
                chPtr2 += 10;
                length -= 10;
            }
            while (length > 0)
            {
                if (*(((int*) chPtr)) != *(((int*) chPtr2)))
                {
                    break;
                }
                chPtr += 2;
                chPtr2 += 2;
                length -= 2;
            }
            return (length <= 0);
        }
    }
}

(2) C#下String类也是引用类型,它通过重写Equals方法和==关系运算,最终都是通过EqualsHelper方法进行对字符串的比较,所以Equals和==实质上是相同的,没有区别。

(3) 根据以上分析,所以返回值都是True,都是根据字符串是否相同进行比较,跟对象引用没有关系,导致结果是让人感觉String是个值类型,其实是微软通过对String进行改造,使之像是个值类型而已。

6、总结

(1) C#下String类中的==已经不是一个纯粹的关系运算符了,它的作用是比较两个字符串是否一样的,同Equals方法一致。

(2) 为什么微软将==改写,而不是保持==是个关系运算符的本质呢?

MSDN上的话是:”尽管 string 是引用类型,但定义相等运算符(== 和 !=)是为了比较 string 对象(而不是引用)的值。 这使得对字符串相等性的测试更为直观。”

但是我想,这个根本原因是理念不同导致的。大家都知道C#是后来借鉴Java来设计的,当初要修改Java的String设计,对==进行重写,初衷是为了更方便用户的理解,所以就算稍微违反一些语言程序的统一性、完成性、独立性等等各种性,也要修改这个设计,这就是一个微软对于商业产品的理念:”用户体验”更重要。

发布了74 篇原创文章 · 获赞 74 · 访问量 5万+

猜你喜欢

转载自blog.csdn.net/chybin500/article/details/78931129