java中的字符串的拼接可以分为下面三种情况:
1. 字符串常量与字符串常量的拼接
String s1 = "a"+"b"+"c";
2. 字符串常量与变量的拼接
String s1 = "a"; String s2 = s1+"b";
3. 变量与变量的拼接
String s1 = "a"; String s2 = "b"; String s4 = s1+s2;
(1)常量与常量的拼接原理:
字符串常量之间的拼接操作在未加载到内存之前就已经完成了。
在前端编译期间(即将.java源文件编译为.class字节码文件),会对字符串常量之间的拼接操作进行优化。
对应的指令:
可以看到对于s1和s2这两个局部变量,它们指向的是常量池中同一个对象,它们存储的都是常量池中"abc"对象的地址。所以在指向==运算时其结果为true。
(2)变量参与的字符串拼接操作底层原理:
字符串拼接操作中只要其中有一个是变量,结果就在堆中。且变量拼接的原理是创建一个StringBuilder类的对象,调用其append方法,拼接完成后再调用该对象的toString()方法(该方法已经被重写了)返回一个字符串对象
public void test1(){ String s1 = "a"; String s2 = "b"; String s3 = "ab"; String
s4= s1+s2; System.out.println(s3==s4);//结果为false }
对应的字节码:
0 ldc #5 <a> 2 astore_1 3 ldc #6 <b> 5 astore_2 6 ldc #7 <ab> 8 astore_3 9 new
#8 <java/lang/StringBuilder> 12 dup 13 invokespecial #9 <java/lang/StringBuilder
.<init>> 16 aload_1 17 invokevirtual #10 <java/lang/StringBuilder.append> 20
aload_221 invokevirtual #10 <java/lang/StringBuilder.append> 24 invokevirtual #
11 <java/lang/StringBuilder.toString> 27 astore 4 29 getstatic #3 <java/lang/
System.out> 32 aload_3 33 aload 4 35 if_acmpne 42 (+7) 38 iconst_1 39 goto 43 (+
4) 42 iconst_0 43 invokevirtual #4 <java/io/PrintStream.println> 46 return
* 加载字符串常量池中的a,将a的地址存储在局部变量表1的位置,即变量s1中
* 加载字符串常量池中的b,将b的地址存储在局部变量表2的位置,即变是s2
* 加载字符串常量池中的ab,将ab的地址存储在局部变量表2的位置,即变量s3中
* 在对以局部变量s1和s2进行字符串拼接的时候,首先创建了一个StringBuilder对象,调用其构造器方法init().
* 将局部变量表中的下标为1、2的变量加载到操作数栈中,然后调用append方法添加到StringBuider对象的末尾,即实现了a、b字符串的连接
* 最后调用StringBuilder的toString方法生成—个新的String对象返回
特别注意:
StringBuilder的toString()方法调用的时String重载的构造器方法,是以字符数组为字符串实际内容进行创建的,并未直接以字面量方式进行创建String对象,即:
由于未直接出现字面量("abc"这种,下面s1、s2、s3等号右边都直接出现了字面量,会在字符串常量池创建对应的对象),不会在字符串常量池中创建对应的对象。StringBuilder调用toString()方法创建的String对象会直接在堆中为其分配内存,常量池中不存在对应的对象
public void test1(){ String s1 = "a"; String s2 = "b"; String s3 = "ab"; String
s4= s1+s2; System.out.println(s3==s4);//结果为false }
所以s3引用指向的时字符串常量池中的"ab"对象,而s4指向的时堆中的"ab"对象,二者指向的地址不同,所以进行==操作的结果为false。