【119期】面试官问:了解过 JDK8 中常量池吗?说说运行时的常量池!

29 阅读8分钟

System.out.println(str2 == str2.intern());

String str3 = new StringBuilder("hello").toString();

System.out.println(str3.intern());

System.out.println(str3 == str3.intern());

}

}

请写出控制台中打印的结果?

答案:

helloWorld

true

java

false

hello

false

你答对了吗?

为什么会出现这种答案呢?

== 运算,对于非基本类型比较的是内存地址值,要知道这道题的输出的结果,最简单的方法就是知道对象:str1str1.intern()str2str2.intern()str3str3.intern()的内存地址值就能判断输出是 true还是false。

但这是jvm极力要遏制的,因此我是无法得知内存地址的,当然如果通过其它方式是可以做到的,但是jvm是没有这个api的,下面我们通过hashCode侧面排除一些情况,如果hash值不同,肯定不是同一个对象,hash值相同可能不是同一个对象。公众号Java精选,回复Java面试,获取最新最全面试题,支持在线随时随地刷题。

hash值相同不能确认是同一个对象,但是能得知不同则一定不是同一个对象。也就是下面"java"。str1和str2是两个不同的对象,str1来自堆的Eden区中,而str2则是Old区的字符串常量池中。

public class GCTest {

public static void main(String[] args) {

String str1 = new StringBuilder("ja").append("va").toString();

String str2 = str1.intern();

System.out.println(str1==str2);

System.out.println(System.identityHashCode(str1)); // 通过System提供的方法得到hash值,打印hash值相当于内存地址,hashCode方法不完全等于

System.out.println(System.identityHashCode(str2)); // 打印地址

String str3 = new StringBuilder("hello").append("world").toString();

String str4 = str3.intern();

System.out.println(str3==str4);

System.out.println(System.identityHashCode(str3));

System.out.println(System.identityHashCode(str4));

}

}

得到下面一组打印输出,同一个对象的hash值肯定是相同的,而下面str1和str2的hash值不同肯定不是同一个对象(注意不能调用String重写的hashCode(),我们要调用Object提供的native修饰的hashCode()或者利用System.identityHashCode()得到hash值(这种情况就相当于是内存地址))

false    // str1和str2不是同一个对象

460141958     // 堆空间创建的那个对象 ”java“

1163157884   // 字符串常量池中的字符串对象

true    // 说明str3和str4是同一个对象

1956725890   // 堆中创建的字符串对象 ”helloworld“

1956725890   // 堆中创建的字符串对象 ”helloworld“

先讲一讲前面2行的由来

helloWorld

true

我们都知道String类有一个intern()方法,它的作用就是将字符串存入常量池中,并且方法执行完后将这个字符串对象返回。更多面试题推荐公众号Java精选,回复Java面试,获取最新最全面试题,支持在线随时随地刷题。

不难理解第一次打印前在常量池中没有helloWorld字符串,因此会将这个对象存入常量池中。然后返回字符串打印了第一个helloWorld字符串。在比较==的 intern()方法返回的是常量池中的字符串对象(也是前面创建的对象,两个对象是同一个对象),所以返回了第一个true

为了证明,我们可以通过javap -v TestDemo.class命令将字节码文件反编译得到如下字节码。

下面请阅读一遍:

字节码中分常量池、方法test01是我们重点关注的地方,首先注意到常量池中已经有了hello、World、ja、va字符串。

原因在于我们代码中使用String str1 = new StringBuilder("hello").append("World").toString();这里面"hello"这类就是常量,接着我们直接读test01方法

public class com.example.demo.test.TestDemo

minor version: 0

major version: 58

flags: (0x0021) ACC_PUBLIC, ACC_SUPER

this_class: #47 // com/example/demo/test/TestDemo

super_class: #2 // java/lang/Object

interfaces: 0, fields: 0, methods: 2, attributes: 1

Constant pool:

#1 = Methodref #2.#3 // java/lang/Object."":()V

#2 = Class #4 // java/lang/Object

#3 = NameAndType #5:#6 // "":()V

#4 = Utf8 java/lang/Object

#5 = Utf8

#6 = Utf8 ()V

#7 = Class #8 // java/lang/StringBuilder

#8 = Utf8 java/lang/StringBuilder

#9 = String #10 // 常量"hello"字符串

#10 = Utf8 hello

#11 = Methodref #7.#12 // java/lang/StringBuilder."":(Ljava/lang/String;)V

#12 = NameAndType #5:#13 // "":(Ljava/lang/String;)V

#13 = Utf8 (Ljava/lang/String;)V

#14 = String #15 // 常量"World"字符串

#15 = Utf8 World

#16 = Methodref #7.#17 // java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;

#17 = NameAndType #18:#19 // append:(Ljava/lang/String;)Ljava/lang/StringBuilder;

#18 = Utf8 append

#19 = Utf8 (Ljava/lang/String;)Ljava/lang/StringBuilder;

#20 = Methodref #7.#21 // java/lang/StringBuilder.toString:()Ljava/lang/String;

#21 = NameAndType #22:#23 // toString:()Ljava/lang/String;

#22 = Utf8 toString

#23 = Utf8 ()Ljava/lang/String;

#24 = Fieldref #25.#26 // java/lang/System.out:Ljava/io/PrintStream;

#25 = Class #27 // java/lang/System

#26 = NameAndType #28:#29 // out:Ljava/io/PrintStream;

#27 = Utf8 java/lang/System

#28 = Utf8 out

#29 = Utf8 Ljava/io/PrintStream;

#30 = Methodref #31.#32 // java/lang/String.intern:()Ljava/lang/String;

#31 = Class #33 // java/lang/String

#32 = NameAndType #34:#23 // intern:()Ljava/lang/String;

#33 = Utf8 java/lang/String

#34 = Utf8 intern

#35 = Methodref #36.#37 // java/io/PrintStream.println:(Ljava/lang/String;)V

#36 = Class #38 // java/io/PrintStream

#37 = NameAndType #39:#13 // println:(Ljava/lang/String;)V

#38 = Utf8 java/io/PrintStream

#39 = Utf8 println

#40 = Methodref #36.#41 // java/io/PrintStream.println:(Z)V

#41 = NameAndType #39:#42 // println:(Z)V

#42 = Utf8 (Z)V

#43 = String #44 // 常量"ja"字符串

#44 = Utf8 ja

#45 = String #46 // 常量"va"字符串

#46 = Utf8 va

#47 = Class #48 // com/example/demo/test/TestDemo

#48 = Utf8 com/example/demo/test/TestDemo

#49 = Utf8 Code

#50 = Utf8 LineNumberTable

#51 = Utf8 LocalVariableTable

#52 = Utf8 this

#53 = Utf8 Lcom/example/demo/test/TestDemo;

#54 = Utf8 test01

#55 = Utf8 str1

#56 = Utf8 Ljava/lang/String;

#57 = Utf8 str2

#58 = Utf8 str3

#59 = Utf8 StackMapTable

#60 = Utf8 RuntimeVisibleAnnotations

#61 = Utf8 Lorg/junit/Test;

#62 = Utf8 SourceFile

#63 = Utf8 TestDemo.java

{

public com.example.demo.test.TestDemo();

descriptor: ()V

flags: (0x0001) ACC_PUBLIC

Code:

stack=1, locals=1, args_size=1

0: aload_0

1: invokespecial #1 // Method java/lang/Object."":()V

4: return

LineNumberTable:

line 7: 0

LocalVariableTable:

Start Length Slot Name Signature

0 5 0 this Lcom/example/demo/test/TestDemo;

public void test01();

descriptor: ()V

flags: (0x0001) ACC_PUBLIC

Code:

stack=3, locals=4, args_size=1

0: new #7 // 创建 java/lang/StringBuilder 对象

3: dup

4: ldc #9 // 入栈常量池(#9)中的字符串常量hello

6: invokespecial #11 // 实例初始化将hello传入

9: ldc #14 // 入栈常量池(在#14)中的字符串常量World

11: invokevirtual #16 // StringBuilder.append方法调用传入World

14: invokevirtual #20 // 调用StringBuilder.toString方法

17: astore_1 // 将返回的值的地址引用存入到局部变量1

18: getstatic #24 // 获取打印流

21: aload_1 // 将局部变量1装载成引用类型,也就是"helloWorld"

22: invokevirtual #30 // "helloWorld"调用String.intern:()方法

25: invokevirtual #35 // 调用打印流,打印"helloWorld"

28: getstatic #24 // 获取打印流

31: aload_1 // 加载局部变量1 也就是"helloWorld"

32: aload_1 // 加载局部变量1 "helloWorld"

33: invokevirtual #30 // "helloWorld"调用intern方法

36: if_acmpne 43 // 如果条件满足就转执行43 的iconst_0也就是将0入栈

39: iconst_1 // 将int类型常量值1压入栈

40: goto 44 // 无条件转移到44

43: iconst_0 // 将0入栈

44: invokevirtual #40 // 调用方法传入也就是“ja"(对应常量池的#44)

47: new #7 // 创建StringBuilder对象

50: dup

51: ldc #43 // 加载字符串"ja"

53: invokespecial #11 // 实例化StringBuilder

56: ldc #45 // 加载字符串"va"

58: invokevirtual #16 // 调用append方法

61: invokevirtual #20 // 调用toString方法

64: astore_2 // 将toString的结果存入局部变量2中

65: getstatic #24 // 获取打印流

68: aload_2 // 加载局部变量2的值也就是加载"java"

69: invokevirtual #30 // 调用intern方法

72: invokevirtual #35 // 打印"java"

75: getstatic #24 // 获取打印流

78: aload_2 // 加载局部变量2"java"

79: aload_2 // 加载局部变量2"java"

80: invokevirtual #30 // 调用intern方法

83: if_acmpne 90 // 如果条件成立跳转到90也就是将0入栈

86: iconst_1 // 将常量值1入栈

87: goto 91 // 无条件跳转到91

90: iconst_0 // 将常量值0入栈

91: invokevirtual #40 // 打印

94: new #7 // class java/lang/StringBuilder

97: dup

98: ldc #9 // String hello

100: invokespecial #11 // Method java/lang/StringBuilder."":(Ljava/lang/String;)V

103: invokevirtual #20 // Method java/lang/StringBuilder.toString:()Ljava/lang/String;

106: astore_3

107: getstatic #24 // Field java/lang/System.out:Ljava/io/PrintStream;

110: aload_3

111: invokevirtual #30 // Method java/lang/String.intern:()Ljava/lang/String;

114: invokevirtual #35 // Method java/io/PrintStream.println:(Ljava/lang/String;)V

117: getstatic #24 // Field java/lang/System.out:Ljava/io/PrintStream;

120: aload_3

121: aload_3

122: invokevirtual #30 // Method java/lang/String.intern:()Ljava/lang/String;

125: if_acmpne 132

128: iconst_1

129: goto 133

132: iconst_0

133: invokevirtual #40 // Method java/io/PrintStream.println:(Z)V

136: return

LineNumberTable:

line 13: 0

line 14: 18

line 15: 28

line 17: 47

line 18: 65

line 19: 75

line 21: 94

line 22: 107

line 23: 117

line 24: 136

LocalVariableTable:

Start Length Slot Name Signature

0 137 0 this Lcom/example/demo/test/TestDemo;

18 119 1 str1 Ljava/lang/String;

65 72 2 str2 Ljava/lang/String;

107 30 3 str3 Ljava/lang/String;

StackMapTable: number_of_entries = 6

frame_type = 255 /* full_frame */

offset_delta = 43

locals = [ class com/example/demo/test/TestDemo, class java/lang/String ]

stack = [ class java/io/PrintStream ]

frame_type = 255 /* full_frame */

offset_delta = 0

locals = [ class com/example/demo/test/TestDemo, class java/lang/String ]

stack = [ class java/io/PrintStream, int ]

frame_type = 255 /* full_frame */

offset_delta = 45

locals = [ class com/example/demo/test/TestDemo, class java/lang/String, class java/lang/String ]

stack = [ class java/io/PrintStream ]

frame_type = 255 /* full_frame */

offset_delta = 0

locals = [ class com/example/demo/test/TestDemo, class java/lang/String, class java/lang/String ]

stack = [ class java/io/PrintStream, int ]

frame_type = 255 /* full_frame */

offset_delta = 40

locals = [ class com/example/demo/test/TestDemo, 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 com/example/demo/test/TestDemo, class java/lang/String, class java/lang/String, class java/lang/String ]

stack = [ class java/io/PrintStream, int ]

RuntimeVisibleAnnotations:

0: #61()

org.junit.Test

}

SourceFile: "TestDemo.java"

继续向下讲解:

java

false