环境
java:1.7
例子一
package test.byteCode;
public class StringBuilderTest {
public static void main(String[] args) {
String v1 = "abc";
String v2 = "a";
String v3 = "bc";
System.out.println(v1 == (v2+v3));
}
}
答案是false
。
从字节码的角度分析:
(编译的字节码在下面)
说明:
1、 undefined表示该变量没有初始化
2、| 左边是局部变量,0表示第一个局部变量,依次类推,| 右边是栈底到栈顶元素
3、赋值给slot为1的元素 可以理解为 赋值给 slot 为1 的变量
[0:undefined,1:un,2:un:, 3:un | "abc"] -- ldc 加载常量"abc"
[0:undefined,1:un,2:un:, 3:un | "abc"] -- astore_1 将栈顶元素弹出赋值给slot为1的元素
[0:undefined,1:"abc",2:un:, 3:un | ]
[0:undefined,1:"abc",2:un:, 3:un | "a"] -- ldc 加载常量"a"
[0:undefined,1:"abc",2:un:, 3:un | "a"] -- astore_2 将栈顶元素弹出,赋值给slot为2的元素
[0:undefined,1:"abc",2:"a":, 3:un | ]
[0:undefined,1:"abc",2:"a":, 3:un | "bc" ] -- ldc 加载常量"bc"
[0:undefined,1:"abc",2:"a":, 3:"bc" | ] -- astore_3 将栈顶元素弹出,赋值给slot为3的元素
[0:undefined,1:"abc",2:"a":, 3:"bc" | ] getstatic -- 加载静态字段System.out,并压入栈顶
[0:undefined,1:"abc",2:"a":, 3:"bc" | "System.out" ] 其是PrintStream类型的
[0:undefined,1:"abc",2:"a":, 3:"bc" | "System.out" ] -- aload_1 将slot为1的变量的值压入栈顶
[0:undefined,1:"abc",2:"a":, 3:"bc" | "System.out", "abc"]
[0:undefined,1:"abc",2:"a":, 3:"bc" | "System.out", "abc"] -- new 创建一个对象并压入栈顶
[0:undefined,1:"abc",2:"a":, 3:"bc" | "System.out", "abc", "StringBuilder"]
[0:undefined,1:"abc",2:"a":, 3:"bc" | "System.out", "abc", "StringBuilder"] -- dup 复制栈顶元素并压入栈顶
[0:undefined,1:"abc",2:"a":, 3:"bc" | "System.out", "abc", "StringBuilder", "StringBuilder"]
[0:undefined,1:"abc",2:"a":, 3:"bc" | "System.out", "abc", "StringBuilder", "StringBuilder"] -- aload_2 将slot为2的变量值压入栈顶
[0:undefined,1:"abc",2:"a":, 3:"bc" | "System.out", "abc", "StringBuilder", "StringBuilder", "a"]
[0:undefined,1:"abc",2:"a":, 3:"bc" | "System.out", "abc", "StringBuilder", "StringBuilder", "a"] --invokestatic 将栈顶元素"a"弹出并作
为参数传给String.valueOf()方法,并将返回值压入栈顶
[0:undefined,1:"abc",2:"a":, 3:"bc" | "System.out", "abc", "StringBuilder", "StringBuilder", "a"]
[0:undefined,1:"abc",2:"a":, 3:"bc" | "System.out", "abc", "StringBuilder", "StringBuilder", "a"] --invokespecial 弹出栈顶两个元素并将
参数"a"传入给StringBuilder构造器
[0:undefined,1:"abc",2:"a":, 3:"bc" | "System.out", "abc", "StringBuilder"]
[0:undefined,1:"abc",2:"a":, 3:"bc" | "System.out", "abc", "StringBuilder" ] --aload_3 将slot为3的变量的值压入栈顶
[0:undefined,1:"abc",2:"a":, 3:"bc" | "System.out", "abc", "StringBuilder", "bc"]
[0:undefined,1:"abc",2:"a":, 3:"bc" | "System.out", "abc", "StringBuilder", "bc"] --invokevirtual 将栈顶的两个元素弹出并将bc作为参数传
给append方法,a.append("bc") 返回"abc"压入栈顶
[0:undefined,1:"abc",2:"a":, 3:"bc" | "System.out", "abc", "abc"]
[0:undefined,1:"abc",2:"a":, 3:"bc" | "System.out", "abc", "abc"] --invokevirtual将栈顶变量弹出执行"abc".toString(),并将返回值,压入栈
顶
[0:undefined,1:"abc",2:"a":, 3:"bc" | "System.out", "abc", "abc"]
[0:undefined,1:"abc",2:"a":, 3:"bc" | "System.out", "abc", "abc"] --if_acmpne 将栈顶两个引用型变量弹出进行比较,如果不等跳转至第38(字
节码中编号)指令 很显然 由于后者是使用append方法进行拼接而来,所以不相等,跳转38行
[0:undefined,1:"abc",2:"a":, 3:"bc" | "System.out"]
[0:undefined,1:"abc",2:"a":, 3:"bc" | "System.out","0"] --iconst_0加载int型常量0
[0:undefined,1:"abc",2:"a":, 3:"bc" | "System.out","0"] -- 弹出栈顶两个元素,其中"0"作为参数,执行"System.out.printlt(0)"方法
从上面可以看出,其是通过append()
方法来拼接字符串的,所以其返回false
.
字节码源码
我们先来看下,它的字节码:
执行:javap -v .\StringBuilderTest > test.txt
Classfile /D:/sts/workspace/execlTest/build/classes/test/byteCode/StringBuilderTest.class
Last modified 2018-5-24; size 1013 bytes
MD5 checksum 803eb113d48257bb3b567c4fe2fad0f2
Compiled from "StringBuilderTest.java"
public class test.byteCode.StringBuilderTest
minor version: 0
major version: 51
flags: ACC_PUBLIC, ACC_SUPER
Constant pool:
#1 = Class #2 // test/byteCode/StringBuilderTest
#2 = Utf8 test/byteCode/StringBuilderTest
#3 = Class #4 // java/lang/Object
#4 = Utf8 java/lang/Object
#5 = Utf8 <init>
#6 = Utf8 ()V
#7 = Utf8 Code
#8 = Methodref #3.#9 // java/lang/Object."<init>":()V
#9 = NameAndType #5:#6 // "<init>":()V
#10 = Utf8 LineNumberTable
#11 = Utf8 LocalVariableTable
#12 = Utf8 this
#13 = Utf8 Ltest/byteCode/StringBuilderTest;
#14 = Utf8 main
#15 = Utf8 ([Ljava/lang/String;)V
#16 = String #17 // abc
#17 = Utf8 abc
#18 = String #19 // a
#19 = Utf8 a
#20 = String #21 // bc
#21 = Utf8 bc
#22 = Fieldref #23.#25 // java/lang/System.out:Ljava/io/PrintStream;
#23 = Class #24 // java/lang/System
#24 = Utf8 java/lang/System
#25 = NameAndType #26:#27 // out:Ljava/io/PrintStream;
#26 = Utf8 out
#27 = Utf8 Ljava/io/PrintStream;
#28 = Class #29 // java/lang/StringBuilder
#29 = Utf8 java/lang/StringBuilder
#30 = Methodref #31.#33 // java/lang/String.valueOf:(Ljava/lang/Object;)Ljava/lang/String;
#31 = Class #32 // java/lang/String
#32 = Utf8 java/lang/String
#33 = NameAndType #34:#35 // valueOf:(Ljava/lang/Object;)Ljava/lang/String;
#34 = Utf8 valueOf
#35 = Utf8 (Ljava/lang/Object;)Ljava/lang/String;
#36 = Methodref #28.#37 // java/lang/StringBuilder."<init>":(Ljava/lang/String;)V
#37 = NameAndType #5:#38 // "<init>":(Ljava/lang/String;)V
#38 = Utf8 (Ljava/lang/String;)V
#39 = Methodref #28.#40 // java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
#40 = NameAndType #41:#42 // append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
#41 = Utf8 append
#42 = Utf8 (Ljava/lang/String;)Ljava/lang/StringBuilder;
#43 = Methodref #28.#44 // java/lang/StringBuilder.toString:()Ljava/lang/String;
#44 = NameAndType #45:#46 // toString:()Ljava/lang/String;
#45 = Utf8 toString
#46 = Utf8 ()Ljava/lang/String;
#47 = Methodref #48.#50 // java/io/PrintStream.println:(Z)V
#48 = Class #49 // java/io/PrintStream
#49 = Utf8 java/io/PrintStream
#50 = NameAndType #51:#52 // println:(Z)V
#51 = Utf8 println
#52 = Utf8 (Z)V
#53 = Utf8 args
#54 = Utf8 [Ljava/lang/String;
#55 = Utf8 v1
#56 = Utf8 Ljava/lang/String;
#57 = Utf8 v2
#58 = Utf8 v3
#59 = Utf8 StackMapTable
#60 = Class #54 // "[Ljava/lang/String;"
#61 = Utf8 SourceFile
#62 = Utf8 StringBuilderTest.java
{
public test.byteCode.StringBuilderTest();
descriptor: ()V
flags: ACC_PUBLIC
Code:
stack=1, locals=1, args_size=1
0: aload_0
1: invokespecial #8 // Method java/lang/Object."<init>":()V
4: return
LineNumberTable:
line 3: 0
LocalVariableTable:
Start Length Slot Name Signature
0 5 0 this Ltest/byteCode/StringBuilderTest;
public static void main(java.lang.String[]);
descriptor: ([Ljava/lang/String;)V
flags: ACC_PUBLIC, ACC_STATIC
Code:
stack=5, locals=4, args_size=1
0: ldc #16 // String abc
2: astore_1
3: ldc #18 // String a
5: astore_2
6: ldc #20 // String bc
8: astore_3
9: getstatic #22 // Field java/lang/System.out:Ljava/io/PrintStream;
12: aload_1
13: new #28 // class java/lang/StringBuilder
16: dup
17: aload_2
18: invokestatic #30 // Method java/lang/String.valueOf:(Ljava/lang/Object;)Ljava/lang/String;
21: invokespecial #36 // Method java/lang/StringBuilder."<init>":(Ljava/lang/String;)V
24: aload_3
25: invokevirtual #39 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
28: invokevirtual #43 // Method java/lang/StringBuilder.toString:()Ljava/lang/String;
31: if_acmpne 38
34: iconst_1
35: goto 39
38: iconst_0
39: invokevirtual #47 // Method java/io/PrintStream.println:(Z)V
42: return
LineNumberTable:
line 13: 0
line 14: 3
line 15: 6
line 17: 9
line 18: 42
LocalVariableTable:
Start Length Slot Name Signature
0 43 0 args [Ljava/lang/String;
3 40 1 v1 Ljava/lang/String;
6 37 2 v2 Ljava/lang/String;
9 34 3 v3 Ljava/lang/String;
StackMapTable: number_of_entries = 2
frame_type = 255 /* full_frame */
offset_delta = 38
locals = [ class "[Ljava/lang/String;", class java/lang/String, class java/lang/String, class java/lang/String ]
stack = [ class java/io/PrintStream ]
frame_type = 255 /* full_frame */
offset_delta = 0
locals = [ class "[Ljava/lang/String;", class java/lang/String, class java/lang/String, class java/lang/String ]
stack = [ class java/io/PrintStream, int ]
}
SourceFile: "StringBuilderTest.java"
例子二
源码
package test.byteCode;
public class StringBuilderTest {
public static void main(String[] args) {
String v1 = "abc";
final String v2 = "a";
final String v3 = "bc";
System.out.println(v1 == (v2+v3));
}
}
结果为true
字节码
Classfile /D:/sts/workspace/execlTest/build/classes/test/byteCode/StringBuilderTest.class
Last modified 2018-5-23; size 762 bytes
MD5 checksum 62874fe9ef2eda28b00fb9cb1e5a4501
Compiled from "StringBuilderTest.java"
public class test.byteCode.StringBuilderTest
minor version: 0
major version: 51
flags: ACC_PUBLIC, ACC_SUPER
Constant pool:
#1 = Class #2 // test/byteCode/StringBuilderTest
#2 = Utf8 test/byteCode/StringBuilderTest
#3 = Class #4 // java/lang/Object
#4 = Utf8 java/lang/Object
#5 = Utf8 <init>
#6 = Utf8 ()V
#7 = Utf8 Code
#8 = Methodref #3.#9 // java/lang/Object."<init>":()V
#9 = NameAndType #5:#6 // "<init>":()V
#10 = Utf8 LineNumberTable
#11 = Utf8 LocalVariableTable
#12 = Utf8 this
#13 = Utf8 Ltest/byteCode/StringBuilderTest;
#14 = Utf8 main
#15 = Utf8 ([Ljava/lang/String;)V
#16 = String #17 // abc
#17 = Utf8 abc
#18 = String #19 // a
#19 = Utf8 a
#20 = String #21 // bc
#21 = Utf8 bc
#22 = Fieldref #23.#25 // java/lang/System.out:Ljava/io/PrintStream;
#23 = Class #24 // java/lang/System
#24 = Utf8 java/lang/System
#25 = NameAndType #26:#27 // out:Ljava/io/PrintStream;
#26 = Utf8 out
#27 = Utf8 Ljava/io/PrintStream;
#28 = Methodref #29.#31 // java/io/PrintStream.println:(Z)V
#29 = Class #30 // java/io/PrintStream
#30 = Utf8 java/io/PrintStream
#31 = NameAndType #32:#33 // println:(Z)V
#32 = Utf8 println
#33 = Utf8 (Z)V
#34 = Utf8 args
#35 = Utf8 [Ljava/lang/String;
#36 = Utf8 v1
#37 = Utf8 Ljava/lang/String;
#38 = Utf8 v2
#39 = Utf8 v3
#40 = Utf8 StackMapTable
#41 = Class #35 // "[Ljava/lang/String;"
#42 = Class #43 // java/lang/String
#43 = Utf8 java/lang/String
#44 = Utf8 SourceFile
#45 = Utf8 StringBuilderTest.java
{
public test.byteCode.StringBuilderTest();
descriptor: ()V
flags: ACC_PUBLIC
Code:
stack=1, locals=1, args_size=1
0: aload_0
1: invokespecial #8 // Method java/lang/Object."<init>":()V
4: return
LineNumberTable:
line 3: 0
LocalVariableTable:
Start Length Slot Name Signature
0 5 0 this Ltest/byteCode/StringBuilderTest;
public static void main(java.lang.String[]);
descriptor: ([Ljava/lang/String;)V
flags: ACC_PUBLIC, ACC_STATIC
Code:
stack=3, locals=4, args_size=1
0: ldc #16 // String abc
2: astore_1
3: ldc #18 // String a
5: astore_2
6: ldc #20 // String bc
8: astore_3
9: getstatic #22 // Field java/lang/System.out:Ljava/io/PrintStream;
12: aload_1
13: ldc #16 // String abc
15: if_acmpne 22
18: iconst_1
19: goto 23
22: iconst_0
23: invokevirtual #28 // Method java/io/PrintStream.println:(Z)V
26: return
LineNumberTable:
line 13: 0
line 14: 3
line 15: 6
line 17: 9
line 18: 26
LocalVariableTable:
Start Length Slot Name Signature
0 27 0 args [Ljava/lang/String;
3 24 1 v1 Ljava/lang/String;
6 21 2 v2 Ljava/lang/String;
9 18 3 v3 Ljava/lang/String;
StackMapTable: number_of_entries = 2
frame_type = 255 /* full_frame */
offset_delta = 22
locals = [ class "[Ljava/lang/String;", class java/lang/String, class java/lang/String, class java/lang/String ]
stack = [ class java/io/PrintStream ]
frame_type = 255 /* full_frame */
offset_delta = 0
locals = [ class "[Ljava/lang/String;", class java/lang/String, class java/lang/String, class java/lang/String ]
stack = [ class java/io/PrintStream, int ]
}
SourceFile: "StringBuilderTest.java"
分析字节码
[0:un, 1:un, 2:un, 3:un | "abc"] --ldc 加载常量"abc"
[0:un, 1:un, 2:un, 3:un | "abc"] -- astore_1 将栈顶元素弹出赋值给slot为1的变量
[0:un, 1:un, 2:"abc", 3:un | ]
[0:un, 1:un, 2:"abc", 3:un | ] -- ldc 加载常量"a"
[0:un, 1:un, 2:"abc", 3:un | "a"]
[0:un, 1:un, 2:"abc", 3:un | "a"] --astore_2 将栈顶元素弹出赋值给slot为2的变量
[0:un, 1:un, 2:"abc", 3:un | ]
[0:un, 1:un, 2:"abc", 3:un | ] -- ldc 加载常量"bc"
[0:un, 1:un, 2:"abc", 3:un | "bc"]
[0:un, 1:un, 2:"abc", 3:un | "bc"] --astore_3 将栈顶元素弹出并赋值给slot为3的变量
[0:un, 1:un, 2:"abc", 3:"bc" | ]
[0:un, 1:un, 2:"abc", 3:"bc" | ] --getstatic 加载PrintStream类的静态字段System.out
[0:un, 1:un, 2:"abc", 3:"bc" | "System.out"]
[0:un, 1:un, 2:"abc", 3:"bc" | "System.out"] --aload_1 将slot为1的变量的值压入栈顶
[0:un, 1:un, 2:"abc", 3:"bc" | "System.out", "abc"]
[0:un, 1:un, 2:"abc", 3:"bc" | "System.out", "abc", "abc"] -- ldc 加载常量abc
[0:un, 1:un, 2:"abc", 3:"bc" | "System.out", "abc", "abc"] -- if_acmpne 弹出栈顶两个引用型元素并进行比较,若不等继续往下执行,否则就跳转到字节码序号为22行的指令
[0:un, 1:un, 2:"abc", 3:"bc" | "System.out"]
其实就是是返回0还是返回1的事情。
[0:un, 1:un, 2:"abc", 3:"bc" | ]
因为其没有去执行append()
方法来拼接abc
。而是编译的时候直接就拼接好了,所以在使用时,是直接加载常量abc
字符串。因此返回的是true
。
总结
虽然很简单,但可以看出final
使得jvm
少干了很多事情!