1.问题
1)
String str1 = "cityu";
String str2 = "cityu";
String str3 = "city"+"u";
System.out.println(str1==str2);
System.out.println(str1==str3);
System.out.println(str2==str3);
2)
String str1 = "cityu";
String str2 = new String("cityu");
String str3 = "city"+new String("u");
System.out.println(str1==str2);
System.out.println(str1==str3);
System.out.println(str2==str3);
3)
String str1 = "cityu";
String str2 = new String("cityu");
String str3 = "city"+new String("u");
System.out.println(str1==str2.intern());
System.out.println(str1==str3.intern());
System.out.println(str2==str3.intern());
以上三个问题都涉及到字符串的包装类String在使用"=="比较的问题。上面三个问题答案的分别是:
1 | 2 | 3 |
true | false | true |
true | false | true |
true | false | false |
2.基础知识
为了解释上面三个问题的答案,需要先了解一些基础知识。
1 )不同数据类型在JVM中的存储方式
我们知道JVM(虚拟机)分为堆和栈,堆是共享区,而栈是私有区。基本数据类型存储在栈中,而且引用数据类型存储在堆中,引用数据类型会将其在堆中的地址保存在栈中。
2)字符串常量池
JVM为了提高性能和减少内存开销,为字符串专门开辟了一个区域,即字符串常量池。在JDK1.7之前,字符串常量池在方法区里面,而在JDK1.7+以后,字符串常量池就被放在了堆中。
3)"=="的作用
1、用于基本数据类型的比较
2、判断引用是否指向堆内存的同一块地址。
3.分析问题
在掌握一些基础知识以后,我们可以开始分析上面的问题了。
String str1 = "cityu";
String str2 = "cityu";
String str3 = "city"+"u";
System.out.println(str1==str2);
System.out.println(str1==str3);
System.out.println(str2==str3);
在第一行代码运行后,JVM会首先在字符串常量池中寻找"cityu",如果存在,则不用在常量池中创建对象,直接返回已存在对象的引用,如果没有就先在常量池中创建一个"cityu",然后返回该对象的的引用。所以在执行第二行和第三行代码时,在字符串常量池中并没有创建新的对象,只是对同一对象的多次引用。所以运行结果都是"true"。
String str1 = "cityu";
String str2 = new String("cityu");
String str3 = "city"+new String("u");
System.out.println(str1==str2);
System.out.println(str1==str3);
System.out.println(str2==str3);
第一行代码执行后,字符串常量池中会有一个"cityu"的常量,在使用new来创建对象时,即执行第二行代码时,如果常量池中有相同字符对象,则在堆中重新创建一个对象,然后返回堆的数据地址。如果字符串常量池中没有,则还需要在常量池中创建对象,一共两个对象,但返回的都是堆中创建的对象的地址。第三行代码则需要在常量池中创建两个对象,在堆中创建一个对象,返回的也是堆数据地址。所以运行结果都是"false"。
String str1 = "cityu";
String str2 = new String("cityu");
String str3 = "city"+new String("u");
System.out.println(str1==str2.intern());
System.out.println(str1==str3.intern());
System.out.println(str2==str3.intern());
当字符串对象调用intern方法时,JVM虚拟机会先去字符串常量池中查找该对象是否在字符串常量池中存在,若存在,则直接返回字符串常量池该对象的引用。若字符串常量池不存在该对象,则直接把堆中的该对象的引用复制到字符串常量池中,然后返回字符串常量池中该引用的值(即堆中该对象的引用)。所以前两行代码运行结果为"true",后面一行为"false"。
4.总结
在比较基本数据和引用地址一般用"==",而有时候我们仅仅是为了比较两个对象的内容是否相同时,需要用到equals(),但是我们在使用equals()方法时要小心,我们知道object是所有类的父类,而object中关于equals()方法的定义如下所示:
public boolean equals(Object obj) {
return (this == obj);
可以看到此时的equals(),就是等同于"=="。所以要使用equals()来实现不同对象内容的比较,就必须重写equals()方法。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;
}