== 和equals() 比较结果不同的原因

对于这个问题,可以帮助我们很好的理解Java对象的创建,赋值以及== 和equals()的用法。

我们通过如下实例来说明,先看一个简单的代码:


public class Practice1 {

	public static void main(String[] args) {
		String str1=new String("hello");
		String str2=new String("hello");
		String str3="hello";
		String str4="hello";
		
		//结果为true
		System.out.println("equals: "+str1.equals(str2));
		//1.结果为true   
		System.out.println("equals: "+str1.equals(str3));
		//2.结果为false  
		System.out.println("str==str1  "+(str1==str3));
		//结果为true
		System.out.println("str2==str1  "+(str3==str4));
    }
}

在回答问题之前,我们需要再来明确某些内容,它们可以帮助我们更好的理解问题的答案。

前文叙述有点长,也可先看第三部分,如果看完之后还不是很懂,可以回来从此再看。

一、先谈创建对象的相关内容

虽然我们都知道Java是面向对象的编程,但并非完全的面向对象。比如说Java中的基本数据类型,用int,double等创建的变量都不是对象。一般我们都是通过new 关键字来创建对象,而基本数据类型创建的变量并不能用new 的方式获取。虽然如此,但Java对基本数据类型也有相应的解决办法——封装与其相应的类,即Integer对应int,Double对应double,它们皆是为了解决基本数据类型面向对象用的。

明确这些之后,我们再来看类型是如何分配的,基本数据类型在栈中进行分配,而对象类型在堆中进行分配。基本类型之间的赋值是创建新的拷贝,而对象之间的赋值只是传递引用。所有方法的参数(基本类型除外)传递的都是引用而非本身的值。


现在,再来聊聊String s="hello";以及String s = new String("hello");

我在之前的一篇博客中简单的提到过String s="hello";(变量&数据类型),它与new不同,同时也是java中唯一不需要new就可以产生对象的途径。这种形式的赋值称为——直接量,它被放在一个叫作字符串拘留池(常量池)的地方;而new 创建的对象都放在堆上。String s="hello" 这种形式的字符串,会在JVM(Java虚拟机)中发生字符串拘留。

那什么是字符串拘留呢?我们通过一个例子来理解这种机制,当我们声明一个字符串String s="hello";时,JVM会先从常量池中查找有没有值为"hello"的对象。如果有,会把该对象赋给当前引用,也就是说原来的引用和现在的引用指向同一个对象;如果没有,则在拘留池中创建一个值为"hello"的对象,如果下一次还有类似的语句,例如String str="hello";时,又会将str指向"hello"这个对象。以这种形式声明字符串,无论有多少个都指向同一个对象。

再来说说String s = new String("hello");

这种形式创建的对象就和其他new 创建的对象一样了,每执行一次就生成一个新对象,也就是说String str = new String("hello");与s毫无关系,他们是两个独立的对象,只不过巧了,他们的值或是说内容相等。


我们也可以简单的理解为:

String str = "hello"; 先在内存中找是否有"hello"这个对象,如果有就可以直接从常量池中拿来用,不用再从内存中创建空间来存储。如果没有,就创建一块新内存存着,以后要是有对象要用就直接给它用了。

String str=new String ("hello") 就是不管内存里有没有"hello"这个对象,都新建一个对象保存"hello"。


        看几个例子:

 String s1 = "qibao"; // 放在常量池中,没找到,新建一个
            String s2 = "qibao"; // 从常量池中查找,找到了,直接引用。s1,s2指向同一个对象

 String s3 = new String("qibao"); // s3 为一个引用
            String s4 = new String("qibao"); // s4 也是一个引用。虽然s3,s4对象的内容一样,但他们却占着两块地。

String s5 = "qi" + "bao"; //字符串常量相加,在编译时就会计算结果,s1 == s5 返回ture

String s6 = "qi"; String s7 = "bao"

String s8 = s6 + s7; //字符串变量相加,编译时无法计算,s1 == s8 返回false

class Person{

    String name;

    Person(String name) { this.name = name;}

}

Person p1 = new Person("qibao");

Person p2 = new Person("qibao");

p1.name == p2.name  //返回true


            


二、再说== 跟equals() 的事

  •  ==

先解释几个名词:

  • 寄存器:最快的存储区, 系统分配,程序中无法控制. 
  • 栈:基本数据类型变量和对象引用的存储区,对象本身不放在栈中,而是存放在堆或者常量池中。
  • 堆:new创建对象的存储区。 
  • 静态域:静态成员变量的存储区。
  • 常量池:基本数据类型常量和字符串常量的存储区。

== 或 !=   比较的是 栈中存放的对象引用 在堆上的地址,
           即判断两对象的堆地址是否相同,即是否是指相同一个堆对象。
         
           对于基本类型,== 和 != 是比较值。
           对于对象来说,== 和 != 是比较两个引用,即判断两个对象的地址是否相同.

  • equals()

 

我们先来看Object中定义的equals()

public boolean equals(Object obj) {
        return (this == obj);
    }

          

Object.equals()使用的算法区分度高,只要两对象不是同一个就是错误的。由于所有的类都继承自Object类,所以equals()适用于所有对象。Object中的equals方法返回 == 的判断,即对象的地址判断。 虽然如此,但可以对Object.equals()进行覆盖,String类则实现覆盖。我们再来看看String.equals():

private final char value[];

public String(String original) {
        this.value = original.value;
        this.hash = original.hash;
    }

public boolean equals(Object anObject) {
        if (this == anObject) {
            return true;
        }
        if (anObject instanceof String) {
            String anotherString = (String)anObject;
            int n = value.length;
            if (n == anotherString.value.length) {
                char v1[] = value;
                char v2[] = anotherString.value;
                int i = 0;
                while (n-- != 0) {
                    if (v1[i] != v2[i])
                        return false;
                    i++;
                }
                return true;
            }
        }
        return false;
    }

 
          查看String对equals覆盖的源码会发现,String.equals()相等的条件是:比较二者同为String类型,长度相等,且字符串值完全相同,包括顺序和值,不再要求两者为同一对象。也可以理解为String.equals()将原本的String对象拆分成单个字符之间值的比较,每个字符的比较完之后返回一个最终的boolean类型的值,即将原本可能指向不同堆地址的两个对象  "间接的" 指向了同一个地址,以到达比较值的目的。
          

         

三、解决问题的时候到了

看完上边这些内容之后,我们再来看这两行代码:

String str1=new String("hello");
String str3="hello";

//1.结果为true   
System.out.println("equals: "+str1.equals(str3));
//2.结果为false  
System.out.println("str==str1  "+(str1==str3));

 

现在,我们就可以很清楚的知道为什么1 返回true:

String.equal() 只看两者是否为String,长度是否相等,以及每个字符的值是否相等,很显然str1和str2满足这三点要求,所以返回结果为真。

至于2 返回false,我们也明白为何了:

== 是对对象地址的判断,而这两种声明方式的存储形式是不同的,因此它们的地址不同,自然返回false了。


 

重载equals()方法

通过上文我们知道可以对Object.equals()实现覆盖,那也就意味着我们可以自己写equals方法,关于这方面的内容,我会在后面的博客覆盖equals方法中说明。

 

(由于本人技术有限,如有不正确的地方,欢迎大家留言指正,本人将不胜感激。)

 

 

 

 

 

 

posted on 2018-07-22 19:22  七宝嘤嘤怪  阅读(568)  评论(0编辑  收藏  举报