解释一下 == 和 equals 的区别,你以为就这么简单?那你就草率了
== 和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架构师联盟,每日更新技术好文
版权声明: 本文为 InfoQ 作者【小Q】的原创文章。
原文链接:【http://xie.infoq.cn/article/adfdec283f5346929c19b7247】。
本文遵守【CC BY-NC-ND】协议,转载请保留原文出处及本版权声明。
评论