==
面试的时候经常会遇到让你说明==
和equals
的区别,通常会拿String类做举例,比如下面代码:
public class Test {
public static void main(String[] args) {
String s1="hello world";
String s2="hello world";
System.out.println(s1 == s2);
}
}
输出结果为:true
但是如果是下面的代码:
public class Test {
public static void main(String[] args) {
String s3=new String("hello world");
String s4=new String("hello world");
System.out.println(s3 == s4);
}
}
输出结果为:false
为什么同样的字符串值,一个是直接赋值给String,一个是通过new实例,2者比较却截然不同。接下来我们先说下==
的结论,然后我们再证明,结论如下:
结论一:比较两个值或对象指向的地址值是否相同。
结论二:如果是基本数据类型,则直接比较其值是否相等。
我们看看结论一:先利用JClassLib工具查看class文件字节码指令信息,从以下图看出,红圈部分就是在使用==
比较判断用的字节码指令
那if_acmpne
是什么意思呢?点击 if_acmp 可以查看它的介绍,总的来说就是if_acmp
就是比较2个的地址是否相等,它是Java虚拟机的一个字节码指令,因此在Java虚拟机里对==
的这种比较是采用它们指向的地址是否相同,那怎么知道2个值或对象指向的地址是否相同呢?由于Java没有直接获取自己的地址的方法,这边可以用写好的 AddressUtil.java 这个工具类可以拿到对象地址,代码如下:
public class Test {
public static void main(String[] args) {
String s1="hello world";
String s2="hello world";
AddressUtil.printAddresses("s1的地址",s1);
AddressUtil.printAddresses("s2的地址",s2);
System.out.println(s1 == s2);
}
}
输出结果为:
s1的地址: 0x76b18c570
s2的地址: 0x76b18c570
true
可以看到s1地址和s2的地址是相同的,因此符合s1 == s2
比较的结果是true
。接下来看下面的代码:
public class Test {
public static void main(String[] args) {
String s3=new String("hello world");
String s4=new String("hello world");
AddressUtil.printAddresses("s3的地址",s3);
AddressUtil.printAddresses("s4的地址",s4);
System.out.println(s3 == s4);
}
}
输出的结果为:
s3的地址: 0x76b18c5d0
s4的地址: 0x76b18c628
false
可以看到s3 的地址和s4的地址是不一样的,因此符合s3 == s4
比较的结果是false
。那么思考一下,为什么s3和s4的地址不相同呢?
我们再来看看结论二:如果是基本数据类型,则直接比较其存储的 “值”是否相等。我们看下面代码:
public class Test {
public static void main(String[] args) {
int a=1;
int b=1;
System.out.println(a==b);
}
}
输出结果为true
,这里a和b都是基本数据类型,我们来看看它是怎么直接比较存储值是否相等的 ,同样用JClassLib工具查看class文件字节码指令信息,如下图:
看到红圈部分if_icmpne
在官网有解释,点击if_icmpne可以查看,总的俩说if_icmpne
指令就是用来比较int值是否相等的,依此类推,你能证明float、double、long、char等等基本数据类型也是比较存储值是否相等吗?
equals
接下来看equals方法,代码如下:
public class Test {
public static void main(String[] args) {
String s1="hello world";
String s2="hello world";
System.out.println(s1.equals(s2));
}
}
输出结果为true
再看下面代码:
public class Test {
public static void main(String[] args) {
String s3=new String("hello world");
String s4=new String("hello world");
System.out.println(s3.equals(s4));
}
}
输出结果为true
从上面看出,不管是直接赋值给String的还是new实例出来的,equals比较都是true,那equals是比较地址的值还是比较的是内容呢?先说下equals的结论:
结论一:默认比较的是两个值或对象指向的地址值是否相同
结论二:String类重写了equals()方法,比较的是内容是否相同
来看看结论一,首先都知道Object是所有类的父类,任何类都默认继承Object,而String的equals方法重写的是Object类equals方法:java.lang.Object#equals
,来看看Object类里equals方法的代码是怎么实现的:
public boolean equals(Object obj) {
return (this == obj);
}
居然默认使用了==
比较,前面说了==
比较是比较的是地址是否相同,因此结论一是对的。
然后再来看结论二,String类重写了equals()方法,来看看java.lang.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;
}
从上面可以看出String的equals方法代码逻辑是比较字符串内容的,依此类推Integer、Double、Float对象是不是也是重写equals了方法。
总结
- ==和equals比较的是两个值或对象指向的地址值是否相同
- 如果比较内容是否相同需要重写equals方法,在重写的方法实现比较内容的逻辑,比如String、Integer类就是重写equals了方法
延伸:对象作为 map 的 key 时,为什么需要重写 equals 方法和 hashCode 方法?