首先来看一道题。
题目描述
问下面两种赋值方式有何区别?
public class Demo {
public static void main(String[] args) {
String s = "1" + "2" + "3";
String s1 = "1";
String s2 = s1 + "2";
String s3 = s2 + "3";
System.out.println(s);
System.out.println(s3);
}
}
分析解答
从表面其实看不出什么,我们可以通过Class文件反编译成的字节码(Byte Code)来分析。
如果你在使用IDEA,请先在IDEA中安装ASMified Bytecode Online插件,安装详细教程->推荐几个可以提高程序员生存技能的效率软件,如果是其他集成环境,请自行Google安装插件教程。
ASMified Bytecode Online插件作用:用于将Class文件反编译成字节码(Byte Code)形式。将上面代码在IDEA中运行后,生成的字节码(Byte Code)如下图所示:
...
public static void main(String[] a) {
ldc "123"
//astore 1
ldc "1"
//astore 2
_new 'java/lang/StringBuilder'
//dup
//INVOKESPECIAL java/lang/StringBuilder.<init> ()V
//aload 2
INVOKEVIRTUAL java/lang/StringBuilder.append (Ljava/lang/String;)Ljava/lang/StringBuilder;
ldc "2"
INVOKEVIRTUAL java/lang/StringBuilder.append (Ljava/lang/String;)Ljava/lang/StringBuilder;
INVOKEVIRTUAL java/lang/StringBuilder.toString ()Ljava/lang/String;
//astore 3
_new 'java/lang/StringBuilder'
//dup
//INVOKESPECIAL java/lang/StringBuilder.<init> ()V
//aload 3
INVOKEVIRTUAL java/lang/StringBuilder.append (Ljava/lang/String;)Ljava/lang/StringBuilder;
ldc "3"
INVOKEVIRTUAL java/lang/StringBuilder.append (Ljava/lang/String;)Ljava/lang/StringBuilder;
INVOKEVIRTUAL java/lang/StringBuilder.toString ()Ljava/lang/String;
....
}
看不懂,没关系!你只需要知道几个指令就能理解了。根据《深入理解Java虚拟机(第二版)》---周志明著的第六章知识可知:
- ldc指令:将一个常量加载到操作数栈。
- _new指令:实例化对象
- invokevirtual指令:用于调用对象的实例方法,根据对象的实际类型进行分派(虚方法分派),这也是Java语言中最常见的方法分派方式。
我们根据字节码顺序来看:
// "1"+"2"+"3"被JVM转换成了字符串常量"123"存储到操作数栈
String s = "1" + "2" + "3";
跳过astore、dup等指令(不是本节重点)
/**
* JVM将"1"存储到操作数栈
* JVM用_new指令实例化一个StringBuilder对象,调用append()方法连接"1"
* JVM将"2"存储到操作数栈
* 调用append()方法连接"2"
* 调用toString()转换成String类型
* JVM_new指令再实例化一个StringBuilder对象,调用append()方法连接"12"
* JVM将"3"存储到操作数栈
* 调用append()方法连接"3"
* 调用toString()转换成String类型
* */
String s1 = "1";
String s2 = s1 + "2";
String s3 = s2 + "3";
当时用使用+
操作符连接字符串时,为什么两者有无字符串对象就有区别呢?
是因为如果不出现字符串引用,字符串常量的值在编译期时就可以确定下来,所以不会使用到StringBuilder;如果出现字符串引用,JVM不能将字符串引用和字符串常量直接连接,所以将在运行期间动态生成StringBuilder对象,让它去实现连接。
说了StringBuilder,就不能不提StringBuffer,两者最大的区别是,前者线程不安全的,后者是线程安全的。不能一看是线程不安全就觉得不好,其实线程不安全比线程安全效率高,再者,因为我们写的一些代码不用线程安全这样多此一举。
特别注意!在循环中连接字符串时,最好不要出现字符串引用,因为每次循环都会新建StringBuilder,即使Java有垃圾回收机制,这样也很浪费资源。这时候就可以用StringBuilder来连接字符串。
public class Demo {
public static void main(String[] args) {
//我们这样写
StringBuilder builder = new StringBuilder();
for (int i = 1; i < 10; i++) {
builder.append(i);
}
System.out.println(builder.toString());
//而不是这样写
String s = "";
for (int i = 1; i < 10; i++) {
s = s + i;
}
System.out.println(s);
}
}
总结
如果此博文谬误,还望各位路过的朋友指正!
网友评论