解释一下 == 和 equals 的区别,你以为就这么简单?那你就草率了

用户头像
小Q
关注
发布于: 2020 年 10 月 14 日

== 和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.结果为trueSystem.out.println("equals: "+str1.equals(str3));//2.结果为falseSystem.out.println("str==str1  "+(str1==str3));

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

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

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

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

* * *

重载equals()方法

============

我们在写程序时,往往有时Java类库中的equals方法不能满足我们的需求。这时,就需要我们自己来定义equals方法。

在写自定义equals方法之前,我们先来看两个类库中已经写好的equals方法。

一、Object.equals()

=================

很简单的一个方法,因为是Object的方法,所以对所有对象都适用。

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

二、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方法时,都做了些什么。

首先是判断是不是自己,如果是自己好办,直接返回true就完事了。

然后如果不是自己,先看看传入的参数是否为String类型,不是返回false就完事。

再然后都是String类型了,在比较长度是否相等,每个字符的值是否相等,全都相等就返回true。

三、自定义equals()

=============

通过模仿String.equals(),我们来写Person.equals()。

class Person {

private String name; 

private int age; 

public Person(String name, int age) { 

this.name = name; 

this.age = age; 

public String getName() {

return name;

public void setName(String name) {

this.name = name; 

public int getAge() {

return age;

} public void setAge(int age) {

this.age = age; } 

public boolean equals(Object another) {

//先判断是不是自己,提高运行效率 

if (this == another)

return true;

//再判断是不是Person类,提高代码的健壮性

if (another instanceof Person) {

//向下转型,父类无法调用子类的成员和方法 

Person anotherPerson = (Person) another;

//最后判断类的所有属性是否相等,其中String类型和Object类型可以用相应的equals()来判断

if ((this.getName().equals(anotherPerson.getName())) && (this.getAge() == anotherPerson.getAge()))

return true; 

} else { 

return false;

}

return false;

}}}

在覆盖equals()时,我们在自定义equals内部调用了Object.equals()和String.equals()。

四、自动生成的equals()

===============

考虑到实际中我们可能会经常覆写equals(),因此eclipse为我们提供自动生成的equals()。

操作过程如上图所示,读者可自行操作查看自动生成的代码。

个人公众号:Java架构师联盟,每日更新技术好文



发布于: 2020 年 10 月 14 日 阅读数: 12
用户头像

小Q

关注

还未添加个人签名 2020.06.30 加入

小Q 公众号:Java架构师联盟 作者多年从事一线互联网Java开发的学习历程技术汇总,旨在为大家提供一个清晰详细的学习教程,侧重点更倾向编写Java核心内容。如果能为您提供帮助,请给予支持(关注、点赞、分享)!

评论

发布
暂无评论
解释一下==和equals的区别,你以为就这么简单?那你就草率了