情景一:不好的字符串拼接习惯
起因是这样的:一个大牛在写了一篇关于java字符串优化问题的讲解,他提到:不要使用strObj+otherValue的方法将otherValue转换为字符串形式,因为底层操作会让你吓一跳的。那么底层的实质是怎么样的呢?他的意思是这样的:
比如: String s = "I have";
int total = 12;
Dog dog = new Dog(); //假设Dog类重写了toString方法
String msg = s + total+dog;
在运行时,msg的赋值语句会执行为: msg = new StringBuilder().append(s).append(total).append(dog.toString()).toString();
我们发现,运行时创建了一个匿名的StringBuilder对象,来拼接字符串,存储到缓冲区,最后调用toString方法返回拼接后的结果。显然,StringBuilder在这里小材大用了,而且消耗了内存资源,不可取。我发现很多人都喜欢写类似 ""+num的代码,我也是。所以以后需要注意了,特别是性能敏感的环境下。应该改用String.valueOf(value),或者包装类型的toString方法,比如Double.toString(value)。
如果需要对字符串进行频繁的修改操作,那就使用StringBuilder,或者StringBuffer(线程安全的),从而避免大量的中间字符串垃圾碎片。
情景二:为什么一个是true,一个是false
一位网友提到了这样一个问题:
String str = "abc";
String str1 = "ab" + "c";
String str2 = "ab";
String str3 = str2 + "c"; //试试运行这段代码,就会发现第一个为true 第二个为false ,why?
下面是我的解释:
这是因为javac的编译优化造成的。str1是由2个字符串常量拼接的,常量一定是不会改变的量,那么在编译阶段,javac就有胆量将它给简化一下:str1就优化为:str1="abc",又因为str也是"abc",所以str和str1共享了内存据“abc”,所以str和str1都指向"abc"。由于==是浅比较,比较的是字符串的内存地址,所以他们相等。
而str3的拼接中包含了一个变量str2,而变量是在运行时动态确定的,所以javac不敢再编译时对它优化。也就是说,str3会在运行时在堆中new出字符串对象"abc",显然它的地址和前面的"abc"的地址不一样,所以是false。
我对上面的程序导出jar后进行反编译,验证了我的猜想,结果如图:
为了进一步证实我的猜想,我把上面的代码改为:
String str = "abc"; final String str2 = "ab";
String str3 = str2 + "c"; System.out.println(str == str3); //打印出true
那么问题来了,不是说str2是变量的吗。怎么这次也打印是true呢?注意我给str2加上了修饰符final,声明str2位常量,那么同样的道理,javac也能保证str2不会改变,于是把str3优化为:str3="abc"。
反编译class文件截图:
注意:不要使用 == 去比较字符串,因为字符串时引用类型,== 比较的是内存地址,而不是字符串内容。请使用equals() 或者equalsIgnoreCase()。
只有字符串常量是共享内存的,而字符串变量或者运行时动态生成的字符串,则非如此。