java字符串常量问题

网上看到的一篇文章讲的字符串常量。其它没什么问题,主要是
str7 == str8 的值为false,表示这个不理解,还望达人们能指点一下。如果能从引用(or地址?)的创建、改变的角度谈谈则再好不过了。

先行谢过~
代码如下(代码已经被我简化过了,只保留有疑问部分):

    public class DemoStringCreation {

        public static void main(String args[]) {
            String s = "lo";
            String str7 = "Hel" + s; 
            String str8 = "He" + "llo";
            System.out.println("    str7 == str8 is " + (str7 == str8));
            System.out.println("    str7.equals(str8) is " + str7.equals(str8));  
        }
    }

/******************************* updated 2014.04.18 14:40 ********************/
@brayden 大大从编译的角度给出了解释,我相信这个解释足够底层、根本,无奈我看不太明白
-_-!!
这篇文章最下面几段给了解释,理解起来似乎有了些感觉。示例代码与相应的解释我也贴一下,
/****************粘贴开始******************/

    String a = "ab";
    String bb = "b";
    String b = "a" + bb;
    System.out.println((a == b)); //result = false   

分析:JVM对于字符串引用,由于在字符串的"+"连接中,有字符串引用存在,而引用的值在程序编译期是无法确定的,即"a" + bb无法被编译器优化,只有在程序运行期来动态分配并将连接后的新地址赋给b。所以上面程序的结果也就为false。

String a = "ab";
final String bb = "b";
String b = "a" + bb;
System.out.println((a == b)); //result = true

分析:和上面例子唯一不同的是bb字符串加了final修饰,对于final修饰的变量,它在编译时被解析为常量值的一个本地拷贝存储到自己的常量 池中或嵌入到它的字节码流中。所以此时的"a" + bb和"a" + "b"效果是一样的。故上面程序的结果为true。

String a = "ab";
final String bb = getBB();
String b = "a" + bb;
System.out.println((a == b)); //result = false

private static String getBB() {
    return "b";
}

分析:JVM对于字符串引用bb,它的值在编译期无法确定,只有在程序运行期调用方法后,将方法的返回值和"a"来动态连接并分配地址为b,故上面 程序的结果为false。
/****************粘贴结束******************/
Ok,这里我的新问题又来了,什么时候是编译期,什么时候是才算是运行期呢?(问题是不是太基础了?我自己都怀疑……装载、编译、运行这些概念是经常碰到,就是从来没有完全弄清楚过程)如果这个问题解决了,那按照上面贴的这篇文章里的解释,就能完全理解通了。或者,能不能把@brayden大大的那个编译解释明白一点点?

阅读 7.8k
4 个回答

==是比较两个字符串引用的地址是否相同,即是否指向同一个对象,而equals方法则比较字符串的内容是否相同。
例如String a = "abc";
String b = "abc";
a == b返回true,a.equals(b)同样返回true,这是为什么呢?
原来程序在运行时有一个字符串池,创建字符串时会先查找池中是否有相应的字符串,如果已经存在的话只需把引用指向它即可,如果没有则新建一个。
上例中创建a时,会在字符串池中首先创建一个"abc",然后a指向它;创建b时,由于"abc"已经存在,b直接指向它即可。
若改为:String a = "abc";
String b = new String("abc");
则a == b返回false,a.equals(b)返回true。因为创建b时不管"abc"是否存在都会new一个新的"abc",从而a和b指向的字符创对象是不同的,因此返回false。

http://blog.sina.com.cn/s/blog_6eef4a860100vdr3.html

sun jdk1.7

String s = "lo";
String str7 = "Hel" + s; 
String str8 = "He" + "llo";

编译为:

 0  ldc <String "lo"> [136]
 2  astore_1 [s]
 3  new java.lang.StringBuilder [138]
 6  dup
 7  ldc <String "Hel"> [140]
 9  invokespecial java.lang.StringBuilder(java.lang.String) [142]
12  aload_1 [s]
13  invokevirtual java.lang.StringBuilder.append(java.lang.String) : java.lang.StringBuilder [143]
16  invokevirtual java.lang.StringBuilder.toString() : java.lang.String [147]
19  astore_2 [str7]
20  ldc <String "Hello"> [150]
22  astore_3 [str8]

0-19行 为新建一个string, 赋给str7; 20-22行 为取常量池中对象, 赋给str8.

所以str7, str8不是一个对象, str7==str8 为false

更新

楼主很用功啊, 我也不偷懒了...

如果真想弄明白, 那下点功夫吧. jvm spec7, 2.5 Run-Time Data Areas, 读一下吧.

这里的ldc指令, 从当前类的常量池把 字符串常量的引用 读入jvm栈. 然后你看到, 它调用了一系列的StringBuilder的方法, 很好理解, 最后的StringBuilder.toString(), 看代码可以知道,

public String toString() {
    // Create a copy, don't share the array
    return new String(value, 0, count);
}

生成了一个新的 String对象, 这个毫无疑问, 此对象是放在堆里的, astore_2 [str7]把此对象的引用赋给str7.

ldc [150]从从当前类的常量池把 字符串常量("Hello")的引用 读入jvm栈, astore_3 [str8]把它赋给str8.

如果更有兴趣的话, 试一下

equals是内容相同比较,可以看下String.equals代码就知道了,==是比较refrence是否相同。

你的疑问s7和s8可以看下String.intern你就明白了,使用s7 == s8.intern()会发现仍然返回的是false,也就是在那个时候,除了s8指向的那个String的内容是"Hello"外,String池中并没有其他内容为"Hello"的字符串,相反,用s7.intern() == s8会返回true,池中已经有了一个内容为"Hello"的String,就是s8指向的那个,由此可以推断,s7赋值的时候并没有String "Hello"生成,具体里面的运行机制我也不是太清楚,你真有兴趣的话可以看下JVM文档

撰写回答
你尚未登录,登录后可以
  • 和开发者交流问题的细节
  • 关注并接收问题和回答的更新提醒
  • 参与内容的编辑和改进,让解决方法与时俱进
推荐问题