在正式讲解equals和==区别之前,让我们先来了解一下对象在JVM内存中的存在形式:
就拿这个Person对象来举例:
Person person=new Person();
person.name="彭于晏";
person.height=185;
class Person{
String name;//名字
int height;//身高
}
为了更好的理解对象在JVM内存中的存在形式
我先将这个Person对象在JVM中的内存分配图画出:
下面具体分析:
首先,当系统执行Person person=new Person();这串代码时会在栈中定义一个变量叫做person,由于Person对象时引用类型,因此他会指向我们调用Person类的无参构造器时在堆区开辟的地址,假设为0x0011,这个地址就用于保存我们的Person对象。
然后,由于我们在Person类中定义了两个属性name和height,因此堆区这个地址中有两个空间,一个用于存放name,一个用于存放height。height的数据类型是int,属于基本数据类型,所以他会把数据直接放在height的这个空间中。而name的数据类型是String类型,属于引用类型,因此,0x0011这个地址中存放的name实际上是一个地址,他的数据 "彭于晏" 是被放在了方法区一个叫常量池的地方,这个数据在常量池也会有一个地址,假设为0x0022,则name中存放的地址就是0x0022。
JVM内存分布介绍完毕,下面看实例
public class Equals {
public static void main(String[] args) {
Person p1=new Person();
p1.name="李华";
Person p2=new Person();
p2.name="李华";
System.out.println("p1==p2的结果为:"+(p1==p2));
System.out.println("p1.name.equals(p2.name)结果为:"+p1.name.equals(p2.name));
System.out.println("p1.equals(p2)结果为:"+p1.equals(p2));
}
}
class Person{
public String name;
}
运行结果如下:
p1==p2的结果为:false
p1.name.equals(p2.name)结果为:true
p1.equals(p2)结果为:false
==是一个比较运算符
(1) ==:既可以判断基本类型,又可以判断引用类型
(2) ==:如果判断基本类型,判断的是值是否相等
(3) ==:如果判断引用类型,判断的是地址是否相等,即判定是不是同一个对象
equals是Object类中的方法
(4)equals:只能判断引用类型
(5)默认判断地址是否相等,子类中往往重写了该方法(后面结合源码分析),用于判断内容是否相等,比如Integer,String
1.第一个输出代码 System.out.println("p1==p2的结果为:"+(p1==p2));中,由于p1和p2都是new出来的对象,因此p1和p2都是指向了各自在堆内存中开辟的一个空间,因此p1和p2地址值不一样,输出为false;
2.第二个输出代码 System.out.println("p1.name.equals(p2.name)结果为:"+p1.name.equals(p2.name));中,使用的是equals方法,我们知道p1.name是一个字符串,而在字符串类型中,equals方法已经被重写,比较的是p1.name和p2.name的值,而p1.name和p2.name的值都是"李华",所以输出true;
3.第三个输出代码 System.out.println("p1.equals(p2)结果为:"+p1.equals(p2));中,p1是一个自定义类,没有重写equals方法,所以这里的equals方法仍然是来自其最高父类Object,比较的是两者的地址,由于p1和p2都是new出来的对象,因此p1和p2都是指向了各自在堆内存中开辟的一个空间,因此p1和p2地址值不一样,输出为false;
Object类中equals的JDK源码如下
public boolean equals(Object obj) {
return (this == obj);
}
很容易看出Object类中的equals方法判断的是否为同一对象
但是在Object的一些子类比如String类中,equals方法被重写,源码如下
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;
}
第一个if语句用于判断传入的对象是不是当前对象,如果是就直接返回true,
第二个if语句用于判断传入的对象是否是String类型,如果是,则先向下转型,将传入的对象强转成String类型,然后再用if语句判断两个字符串长度是否相同,如果相同,再用while循环一个一个的比较字符,只有所以字符都一样,才返回true,否则返回false。因此String类中将equals方法重写用于去判断值是否相等。
equals除了只能判断引用类型外,其底层实现在没有被重写的情况下和==是一致的,都是判断地址是否相等,但在被子类重写的情况下,则是去判断引用类型的内容是否相等。
这里需要注意的是:共有两种方法可以创建引用类型
一种是直接赋值,这种情况下,会把值直接存入常量池,不会重新分配地址,因此这时候如果赋的值相同,则不管是用==比较还是用equals比较,返回的都是true。
另一种是用new的方式,每次new都会重新分配一个地址,所以这时候即使赋值相同,但是两者指向的地址却不同,所以用==比较仍然是会返回false,但由于引用类在继承Object类时对equals进行了改写,比较的是内容,因此在赋值相同情况下,返回true。