话题是由如下的事情引出的:
- public class StringTest {
- public static void main(String[] args) {
- String str1 = new String("abc");
- String str2 = "abc";
- if (str1 == str2) {
- System.out.println("str1 == str2");
- } else {
- System.out.println("str1 != str2");
- }
- String str3 = "abc";
- if (str2 == str3) {
- System.out.println("str2 == str3");
- } else {
- System.out.println("str2 != str3");
- }
- str1 = str1.intern();
- if (str1 == str2) {
- System.out.println("str1 == str2");
- } else {
- System.out.println("str1 != str2");
- }
- String str4 = new String("abc");
- str4 = str4.intern();
- if (str1 == str4) {
- System.out.println("str1 == str4");
- } else {
- System.out.println("str1 != str4");
- }
- }
- }
这段程序的输出是什么?
答案:
- str1 != str2
- str2 == str3
- str1 == str2
- str1 == str4
先看看String类型的对象的产生方法:
String有一个所谓的String constant pool ,是一个特殊的一个空间(注意这个是在栈上)保存String常量。String str = “abc”是先定义一个名为str的对String类的对象引用变量:String str;再先用equals方法(String类覆盖了equals方法)判断这个特殊空间(String constant pool )是否有abc,有则将原来在栈中指向abc的引用赋值给str,否则就在这个特殊空间(String constant pool )上开辟一个存放字面值为"abc"的地址,接着创建一个新的String类的对象o,并将o 的字符串值指向这个地址,而且在栈中这个地址旁边记下这个引用的对象o。最后将str指向对象o的地址。
String str2 = new String("abc")则是在普通堆上创建abc对象。所以str和str2是指向不同的对象,它们是不同的。那么这样话程序的3--18行就好理解了。
而String有个intern()方法,这个方法是个本地方法,它相当于告诉JVM,我这个abc对象是放在特殊空间(String pool)上的。所以20--27打印的结果是相等的。
注意的是:String是final类,一旦创建就无法改变,所以用intern()方法是重新在String pool中创建了一个新的对象。所以28--37打印的结果是相等的。
最后提一句:当比较包装类里面的数值是否相等时,用equals()方法;当测试两个包装类的引用是否指向同一个对象时,用==。
那么就顺带说一说堆和栈。
堆是动态地分配内存大小,生存期也不必事先告诉编译器,垃圾自动回收负责回收,但由于在运行时动态分配内存,存取速度较慢。在堆上分配空间是通过new等指令建立的,类实例化的对象就是从堆上去分配空间的。
栈中主要存放一些基本类型的变量(int、short、 long、byte、float、double、boolean、char)和对象句柄,存取速度比堆要快。注意包装类数据,如Integer, String, Double等将相应的基本数据类型包装起来的类。这些不是在栈上的。
另外,栈数据可以共享。
例如:
int a = 5;
int b = 5;
它的工作方式是这样的。
JVM处理int a = 5,首先在栈上创建一个变量为a的引用,然后去查找栈上是否还有5这个值。如果没有找到,那么就将5存放进来,然后将a指向5。接着处理int b = 5,在创建完b的引用后,因为在栈中已有5这个值,便将b直接指向5。
于是,就出现了a与b同时指向5的内存地址的情况。
下午和一个线上的朋友聊起这些事情,他给了建议去看JVM的相关书籍,还介绍了用javap来看,方法都很好的。