Java中最常见的就是String类,那么很多人都说String是不可变类型,那么如何解释下面的代码?
public class StringTest { public static void main(String[] args) { String a = "A"; a = "B"; System.out.println(a); }}
看到这个问题,突然有点想骂面试官,这是偷换概念好么?
作为一个有经验的码农,立即看出程序结果输出肯定是“B”,此处a确实由“A”变成了“B”,怎么理解呢?
首先,我们默默的打开String的源码(JDK8为例子)
以下为关键部分源码
/** * Strings are constant; their values cannot be changed after they * are created. String buffers support mutable strings. * Because String objects are immutable they can be shared. **/ public final class String implements java.io.Serializable, Comparable, CharSequence { /** The value is used for character storage. */ private final char value[]; /** Cache the hash code for the string */ private int hash; // Default to 0 }
注释中:明确写着String是常量,创建后不可变。
其实面试官,偷换了一个概念,a其实是String的引用,不可变的其实是“A”这个字符串,a作为引用当然可以重新指向到新的String,如“B”。
我们如何证明?
利用javap进行反编译
javap -v StringTest.clas
反编译结果如下:
Last modified 2020-8-25; size 640 bytes MD5 checksum 3ac58ea867e2f73eb49be1e505170898 Compiled from "StringTest.java"public class com.ganhuo.test.StringTest minor version: 0 major version: 52 flags: ACC_PUBLIC, ACC_SUPERConstant pool: #1 = Methodref #7.#24 // java/lang/Object."":()V #2 = String #25 // A #3 = String #26 // B #4 = Fieldref #27.#28 // java/lang/System.out:Ljava/io/PrintStream; #5 = Methodref #29.#30 // java/io/PrintStream.println:(Ljava/lang/String;)V #6 = Class #31 // com/ganhuo/test/StringTest #7 = Class #32 // java/lang/Object #8 = Utf8 #9 = Utf8 ()V #10 = Utf8 Code #11 = Utf8 LineNumberTable #12 = Utf8 LocalVariableTable #13 = Utf8 this #14 = Utf8 Lcom/ganhuo/test/StringTest; #15 = Utf8 main #16 = Utf8 ([Ljava/lang/String;)V #17 = Utf8 args #18 = Utf8 [Ljava/lang/String; #19 = Utf8 a #20 = Utf8 Ljava/lang/String; #21 = Utf8 MethodParameters #22 = Utf8 SourceFile #23 = Utf8 StringTest.java #24 = NameAndType #8:#9 // "":()V #25 = Utf8 A #26 = Utf8 B #27 = Class #33 // java/lang/System #28 = NameAndType #34:#35 // out:Ljava/io/PrintStream; #29 = Class #36 // java/io/PrintStream #30 = NameAndType #37:#38 // println:(Ljava/lang/String;)V #31 = Utf8 com/ganhuo/test/StringTest #32 = Utf8 java/lang/Object #33 = Utf8 java/lang/System #34 = Utf8 out #35 = Utf8 Ljava/io/PrintStream; #36 = Utf8 java/io/PrintStream #37 = Utf8 println #38 = Utf8 (Ljava/lang/String;)V{ public com.ganhuo.test.StringTest(); descriptor: ()V flags: 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 3: 0 LocalVariableTable: Start Length Slot Name Signature 0 5 0 this Lcom/ganhuo/test/StringTest; public static void main(java.lang.String[]); descriptor: ([Ljava/lang/String;)V flags: ACC_PUBLIC, ACC_STATIC Code: stack=2, locals=2, args_size=1 0: ldc #2 // String A 2: astore_1 3: ldc #3 // String B 5: astore_1 6: getstatic #4 // Field java/lang/System.out:Ljava/io/PrintStream; 9: aload_1 10: invokevirtual #5 // Method java/io/PrintStream.println:(Ljava/lang/String;)V 13: return LineNumberTable: line 5: 0 line 6: 3 line 7: 6 line 8: 13 LocalVariableTable: Start Length Slot Name Signature 0 14 0 args [Ljava/lang/String; 3 11 1 a Ljava/lang/String; MethodParameters: Name Flags args}
我们可以看到常量次中有#25和#26两个常量,分别为“A”、“B”
再看具体的main方法
0: ldc #2 // String A2: astore_13: ldc #3 // String B5: astore_1
此处仅仅是将变量重新赋值,并没有修改“A”,因此String确实是一个不可变的。
如果你细心发现,其实包装类都是final进行修饰,都是不可变类,原理同String。
字节码中指令的含义可以自行查询