下面说一下==运算符和equals方法
不得不说,这是个初学者的老大难问题了 其实说起来就是两座大山!覆盖Object类的equals()方法和常量池技术!
之前简单说过==运算符的作用,就是比较基本类型的值是否相等。其实它还可以比较引用是否相等,实质上就是比较对象的内存地址是否相等。
上一篇文章介绍了Object类的两种方法toString()和hashCode(),现在介绍第三种方法:equals()。它的作用是比较两个对象是否相等,默认使用==运算符比较这两个对象的内存地址是否相等(注意equals()不能比较基本类型)。那么问题来了,既然Object类的equals()方法默认使用==来比较对象的内存地址是否相等,那么不同对象岂不是永远不相等了?没错,就是这样!所以如果想要让两个对象相等,就必须覆盖Object类的equals()方法,目的是通过对象内容比较对象是否相等。
同样的,Java核心类库中大多数API已经覆盖了equals()方法,就是根据对象内容判断对象是否相等。
我们来看示例程序:
public class Test21 {
public static void main(String[] args){
//数组
int[] a={1,2,3};
int[] b={1,2,3};
int[] c=new int[]{1,2,3};
int[] d=new int[]{1,2,3};
System.out.println("==和equals的数组验证:");
System.out.println(a==b);//false
System.out.println(a.equals(b));//false
System.out.println(Arrays.equals(a, b));//true
System.out.println(c==d);//false
System.out.println(c.equals(d));//false
System.out.println(Arrays.equals(c, d));//true
//字符串
String str_1="123";
String str_2="123";
String str_3=new String("123");
String str_4=new String("123");
System.out.println("==和equals的字符串验证:");
System.out.println(str_1==str_2);//true
System.out.println(str_1.equals(str_2));//true
System.out.println(str_3==str_4);//false
System.out.println(str_3.equals(str_4));//true
//包装类
Integer i1=10;
Integer i2=10;
Integer i3=new Integer(10);
Integer i4=new Integer(10);
Integer i5=200;
Integer i6=200;
System.out.println("==和equals的字符串验证:");
System.out.println(i1==i2);//true
System.out.println(i1.equals(i2));//true
System.out.println(i3==i4);//false
System.out.println(i3.equals(i4));//true
System.out.println(i5==i6);//false
System.out.println(i5.equals(i6));//true
}
}
首先看数组。由于数组a,b是不同对象,它们的内存地址不同,所以a==b返回false。当调用Object类的原始equals()方法时,实际上还是在做a==b判断,所以仍然返回false。Arrays类的equals()方法覆盖了原始的equals()方法,可以通过数组内容比较数组是否相等,所以Arrays.equals(a,b)返回true。数组c,d同理。
第一座大山解决!
再来看字符串。str_1==str_2居然返回true!wtf!什么鬼!第二座大山出现了!
这是为什么呢?注意!!!Java为了提高内存利用率专门开辟了一块内存区域“运行时常量池”,字符串实现了常量池技术。当使用String s="123";这种格式定义字符串的时候,JVM会先在常量池中寻找是否已经存在"123"字符串,如果存在,就将引用s指向这个"123"字符串;如果不存在,就在常量池新建一个"123"字符串,然后将引用s指向新建的"123"字符串。所以,当JVM执行String str_1="123";语句时,会先在常量池中寻找是否已经存在“123”字符串,由于没有找到,会新建一个“123”字符串并将str_1指向该字符串。然后JVM执行String str_2="123";语句,JVM会在常量池寻找“123”字符串,这次找到了,于是把str_2指向该字符串。所以str_1和str_2实际上引用的是同一个字符串,它们存储的都是"123"字符串的内存地址,显然str_1==str_2自然为true。
注意:使用new操作符创建的字符串是不会放进常量池的,记住只要是new,必然在堆上创建新对象!面试要点:虽然new创建的字符串不会放进常量池,但是比如使用String s=new String("aaa")格式创建字符串对象时,会先在常量池查找是否已经有"aaa"字符串。如果有,就在堆中创建一个"aaa"的拷贝字符串;如果没有,就先在常量池创建一个"aaa"字符串,然后再在堆中创建一个"aaa"对象的拷贝对象。包装类型同理。面试题:String s=new String("aaa"); 产生几个对象?答案:一个或两个,如果常量池已经存在"aaa"对象,就是一个;如果不存在,就是两个。
String类也覆盖了equals()方法,可以通过字符串内容比较字符串是否相等。str_3和str_4引用的字符串是使用new操作符创建的(拷贝常量池中str_1和str_2引用的对象),所以内存地址不同,自然str_3==str_4结果为false。但是由于它们的内容都是“123”,所以str_3.equals(str_4)结果为true。
包装类中的Byte、Character、Boolean、Short、Integer和Long也实现了常量池技术。我们可以看到程序结果也如我们所预期的一样。但是!!常量池只能包含-128~127的整形数值。如果超过这个范围,比如i5和i6,JVM会自动地new对象给它们(Integer i5=new Integer(200)和Integer i6=new Integer(200)),i5==i6的结果就是false。
最后补充三个点:
- 在实际开发中要遵守一个规则:如果两个对象相等,那么哈希值必须相等,也就是a.equals(b)-->a.hashCode()==b.hashCode() 这就意味着覆盖equals()方法之后必须也要覆盖hashCode()方法!
- 上面的规则主要是为了应对容器类的检查,容器类检查两个对象是否相等首先就要检查它们的哈希值是否相等,如果相等才会继续检查equals()方法是否返回true。之所以要第二层检查的原因:由于自定义算法的原因,不同对象的哈希值是有可能相等的。也就是说哈希值是为了缩小检查范围,最后还是要用equals()确认对象是否真的相等。
- StringBuffer和StringBuilder类的equals()方法没有被覆盖,所以即使两个StringBuffer或StringBuilder对象的内容相同,他们调用equals()方法还是会返回false。