JDK版本:1.8
(9之后呢?https://blog.csdn.net/qq_35425070/article/details/90718963)
下面是特殊情况和通常情况的底层探究:
实际代码:
public class aaa {
public static void main(String[] args) {
String a = "abc";
a = a + "d";
}
}
编译器之后的等效代码:
public class aaa {
public static void main(String[] args) {
String a = "abc";
StringBuilder temp = new StringBuilder(a);
temp.append("d");
a = temp.toString();
}
}
来看javap之后的字节码,下面仅贴上主函数:
public static void main(java.lang.String[]);
descriptor: ([Ljava/lang/String;)V
flags: ACC_PUBLIC, ACC_STATIC
Code:
stack=3, locals=2, args_size=1
0: ldc #16 // String abc
2: astore_1
3: new #18 // class java/lang/StringBuilder
6: dup
7: aload_1
8: invokestatic #20 // Method java/lang/String.valueOf:(Ljava/lang/Object;)Ljava/lang/String;
11: invokespecial #26 // Method java/lang/StringBuilder."":(Ljava/lang/String;)V
14: ldc #29 // String d
16: invokevirtual #31 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
19: invokevirtual #35 // Method java/lang/StringBuilder.toString:()Ljava/lang/String;
22: astore_1
23: return
LineNumberTable:
line 7: 0
line 8: 3
line 9: 23
LocalVariableTable:
Start Length Slot Name Signature
0 24 0 args [Ljava/lang/String;
3 21 1 a Ljava/lang/String;
}
可以看到2行的java代码转换成字节码之后有23行,为了让小白也看懂,先来解释一下命令,
ldc 将常量池中的常量值"abc"入栈
astore_1 把栈顶引用类型值保存到变量1,(这里变量1就是a,思考那变量0是什么,看底下本地变量表发现变量0是函数的参数 args,特意把底下变量表也贴上了)
new 创建一个新的对象,从字节码的注释中可以看出编译器在这帮我们new了一个StingBuilde类型的对象
dup 复制栈顶一个字长的数据,将复制后的数据压栈。
aload_1 从局部变量1中装载引用类型值入栈。
invokestatic 调用静态方法。从注释中可以看出调用了String类的ValueOf(Object obj)方法,返回值是String类型,这个是由于参数不匹配时候自动调用的,哪里不匹配了?继续往下看,答案就在下一行。
11: invokespecial #26 // Method java/lang/StringBuilder."
这里调用了StringBuilder的构造方法,哪个对象调用的呢?就是第3行new的时候那个Stringbuilder对象调用的,到源码里看一下是哪个方法究竟,eclipse 里按 ctrl + o 发现了这个方法,他的参数需要一个str类型的变量,你给我的是object类型,聪明的jvm编辑器帮我们调用了一次上面提到的String.ValueOf(Object obj):上一行的结果作为这一行的参数。
将常量池中的常量值"d"入栈
16: invokevirtual #31 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
invokevirtual 运行时方法绑定调用方法。从注释中可以看出来,是调用的StringBuilder.append:(Ljava/lang/String;)方法,贴上jdk源码:
即调用了append方法
19: invokevirtual #35 // Method java/lang/StringBuilder.toString:()Ljava/lang/String;
invokevirtual还是运行时方法绑定调用方法。从注释中可以看出来,是调用的StringBuilder.toString:()方法,贴上jdk源码:
将栈顶引用类型值保存到局部变量1中。这里局部变量1 查局部变量表可以看到是a,即把刚才toString的值赋值给了a(注意是引用)
23: return
方法执行完毕,return
另附我改写的+号拼接java字节码:
public static void main(java.lang.String[]);
descriptor: ([Ljava/lang/String;)V
flags: ACC_PUBLIC, ACC_STATIC
Code:
stack=3, locals=3, args_size=1
0: ldc #16 // String abc
2: astore_1
3: new #18 // class java/lang/StringBuilder
6: dup
7: aload_1
8: invokespecial #20 // Method java/lang/StringBuilder."":(Ljava/lang/String;)V
11: astore_2
12: aload_2
13: ldc #23 // String d
15: invokevirtual #25 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
18: pop
19: aload_2
20: invokevirtual #29 // Method java/lang/StringBuilder.toString:()Ljava/lang/String;
23: astore_1
24: return
LineNumberTable:
line 7: 0
line 8: 3
line 9: 12
line 10: 19
line 11: 24
LocalVariableTable:
Start Length Slot Name Signature
0 25 0 args [Ljava/lang/String;
3 22 1 a Ljava/lang/String;
12 13 2 temp Ljava/lang/StringBuilder;
}
可以看到java8对+拼接字符串做了一定的优化。
public class aaa {
//第一种写法
/* public static void main(String[] args) {
String a = "abc";
StringBuilder temp = new StringBuilder(a);
temp.append("d");
temp.append("e");
temp.append("f");
temp.append("g");
a = temp.toString();
}*/
//第二种写法
/*public static void main(String[] args) {
String a = "abc";
a = a + "d";
a = a + "e";
a = a + "f";
a = a + "g";
}*/
//第三种,特意写出来,没有可比性,实际中不会有人这么写,因为这样写相当于 a = a + "defg".
public static void main(String[] args) {
String a = "abc";
a = a + "d" + "e" + "f" + "g";
}
}
其中推荐第一种写法,查看java字节码发现字节码只有45行,而用拼接方式,字节码长度达到了83行,如果拼接的次数更多,执行的效率差距将更大。
更多java字节码解释:https://blog.csdn.net/qq_35425070/article/detils/84248082