通过该篇文章你将获得:
- 字面量:诸如"abc"、"123"等,由两个引号引起来的部分叫做字面量,其在编译阶段就可确定。
- 常量池:存放常量的地方。
我们将通过以下代码和其字节码理解String的两种创建方式。
package com.pai; public class StringInit { public static void main(String[] args) { String str1 = "abc"; String str2 = new String("def"); } }
经过编译(javac StringInit.java
)与反编译(javap -v StringInit.class
),我们可以查看到如下的常量池:
Constant pool: #1 = Methodref #7.#16 // java/lang/Object."<init>":()V #2 = String #17 // abc #3 = Class #18 // java/lang/String #4 = String #19 // def #5 = Methodref #3.#20 // java/lang/String."<init>":(Ljava/lang/String;)V #6 = Class #21 // com/pai/StringInit #7 = Class #22 // java/lang/Object #8 = Utf8 <init> #9 = Utf8 ()V #10 = Utf8 Code #11 = Utf8 LineNumberTable #12 = Utf8 main #13 = Utf8 ([Ljava/lang/String;)V #14 = Utf8 SourceFile #15 = Utf8 StringInit.java #16 = NameAndType #8:#9 // "<init>":()V #17 = Utf8 abc #18 = Utf8 java/lang/String #19 = Utf8 def #20 = NameAndType #8:#23 // "<init>":(Ljava/lang/String;)V #21 = Utf8 com/pai/StringInit #22 = Utf8 java/lang/Object #23 = Utf8 (Ljava/lang/String;)V
通过常量池我们可以总结如下几点:
#2
和#4
)str1
和str2
)javac StringInit.java
)与反编译(javap -v StringInit.class
),我们可以查看到如下的指令: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 #2 // String abc 2: astore_1 3: new #3 // class java/lang/String 6: dup 7: ldc #4 // String def 9: invokespecial #5 // Method java/lang/String."<init>":(Ljava/lang/String;)V 12: astore_2 13: return LineNumberTable: line 5: 0 line 6: 3 line 7: 13
接下来我们来详细讲解两种创建方式:
0: ldc #2 // String abc 2: astore_1
以下为指令讲解:
#2
字面量推送至栈顶。(压入栈顶)str1
,因此str1引用的是常量池地址。3: new #3 // class java/lang/String 6: dup 7: ldc #4 // String def 9: invokespecial #5 // Method java/lang/String."<init>":(Ljava/lang/String;)V 12: astore_2
以下为指令讲解:
#4
字面量推送至栈顶String <init>(String)
,我们也看到该方法有一个参数,是String类型的,我们使用栈顶的字面量当入参数传入初始化方法。同时我们要知道是哪个个对象需要初始化,因此我们又从栈顶弹出一个引用地址(这也是为什么要dup复制一份的原因)str2
我们可以看出字面量创建只有1个对象,就是常量池中的字面量,本地变量直接引用该地址,入栈1次。
使用对象创建使用了2个对象,常量池和堆,入栈3次。