String不是基本数据类型,String和8种包装类型是不可变类。String和8种基本数据类型采用值传递。

    关于方法区中的常量区和class文件中的常量区的关系,参考:https://www.cnblogs.com/qlqwjy/p/8515872.html

前言:

静态常量池(class文件的常量池)存储的内容:

  常量池要保存的是已确定的字面量值,比如String s = "xxx"的"xxx",再比如new String("xxx")中的"xxx"都会在编译的时候加到class的常量池。也就是说,对于字符串的拼接,纯字面量和字面量的拼接,会把拼接结果作为常量保存到字符串。

  如果在字符串拼接中,有一个参数是非字面量,而是一个变量的话,整个拼接操作会被编译成StringBuilder.append,这种情况编译器是无法知道其确定值的。只有在运行期才能确定。

静态常量池(class文件的常量池)和动态常量池(方法区的常量池)的关系以及区别:

  静态常量池存储的是当class文件被java虚拟机加载进来后存放在方法区的一些字面量和符号引用,字面量包括字符串,基本类型的常量,符号引用其实引用的就是常量池里面的字符串,但符号引用不是直接存储字符串,而是存储字符串在常量池里的索引。

  动态常量池是当class文件被加载完成后,java虚拟机会将静态常量池里的内容转移到动态常量池里,在静态常量池的符号引用有一部分是会被转变为直接引用的,比如说类的静态方法或私有方法,实例构造方法,父类方法,这是因为这些方法不能被重写其他版本,所以能在加载的时候就可以将符号引用转变为直接引用,而其他的一些方法是在这个方法被第一次调用的时候才会将符号引用转变为直接引用的。

0.不可变类的设计原则

  1. public final class String
  2. implements java.io.Serializable, Comparable<String>, CharSequence
  3. {
  4. /** The value is used for character storage. */
  5. private final char value[];//数组是引用传递
  6. /** The offset is the first index of the storage that is used. */
  7. private final int offset;
  8. /** The count is the number of characters in the String. */
  9. private final int count;
  10. /** Cache the hash code for the string */
  11. private int hash; // Default to 0
  12. ....
  13. public String(char value[]) {
  14. this.value = Arrays.copyOf(value, value.length); // deep copy操作
  15. }
  16. ...
  17. public char[] toCharArray() {
  18. // Cannot use Arrays.copyOf because of class initialization order issues
  19. char result[] = new char[value.length];
  20. System.arraycopy(value, 0, result, 0, value.length);
  21. return result;
  22. }
  23. ...
  24. }

如上代码所示,可以观察到以下设计细节:

  1. String类被final修饰,不可继承
  2. string内部所有成员都设置为私有变量
  3. 不存在value的setter
  4. 并将value和offset设置为final。
  5. 当传入可变数组value[]时,进行copy而不是直接将value[]复制给内部变量.
  6. 获取value时不是直接返回对象引用,而是返回对象的copy.

这都符合上面总结的不变类型的特性,也保证了String类型是不可变的类。

例如:

  1. package cn.qlq.test;
  2.  
  3. public class ArrayTest {
  4. public static void main(String[] args) {
  5. String str = "x1x1";
  6. str.replace("1", "2");
  7. System.out.println(str);// x1x1
  8.  
  9. str = str.replace("1", "2");
  10. System.out.println(str);// x2x2
  11. }
  12. }

1.创建过程与字符串拼接过程

1.创建过程研究

例如:

  1. package cn.qlq.test;
  2.  
  3. public class ArrayTest {
  4. public static void main(String[] args) {
  5. String str1 = "abc";
  6. String str2 = "abc";
  7. String str3 = new String("abc");
  8. String str4 = new String("abc");
  9. }
  10. }

  String s1 = new String("abc"); 是在堆中创建一个String对象,并检查常量池中是否有字面量为"abc"的常量,没有的话在常量区创建"abc"并将堆中的对象指向该常量,有的话堆中的对象直接指向"1";

  String s2 = new String("abc"); 又在堆中创建一个String对象,并将s2指向该对象,其字面量"abc"在前面已经创建,所以不会再创建常量区中创建字符串;

  

  String s3 = "abc";   检查常量池中有没有字面量为"abc"的字符串,如果没有则创建并将s3指向该常量;有的话直接指向该该常量;

  String s4 = "abc"  的时候常量池已经有abc,所以不会再创建对象,也就是s3与s4指向同一个对象。

所以我们可以用下面图解解释,String s = new String("xxx")在检查常量池的时候会涉及到堆中创建对象;String s = "x"直接检查常量池,不会涉及堆。

如下图解:

 

一道经典的面试题:new String("abc")创建几个对象?

  简单的回答是一个或者两个,如果是常量区有值为"abc"的值,则只在堆中创建一个对象;如果常量区没有则会在常量区创建"abc",此处的常量区是方法区的运行时常量池(也称为动态常量区)。字面量"abc"也会

被加到常量区。

  我们需要明白只要是new都会在堆中创建对象。直接String s = "xxx"不会涉及堆,只在常量区检查是否有该常量。

反编译查看编译后的信息:  

  1. package cn.qlq.test;
  2.  
  3. public class ArrayTest {
  4. public static void main(String[] args) {
  5. String str1 = "abc";
  6. String str2 = "abc";
  7. String str3 = new String("abc");
  8. String str4 = new String("abc");
  9. }
  10. }

编译并且查看反编译信息:

  1. C:\Users\liqiang\Desktop>javap -c -verbose ArrayTest.class
  2. Classfile /C:/Users/liqiang/Desktop/ArrayTest.class
  3. Last modified 2018-9-2; size 383 bytes
  4. MD5 checksum 0ab23a2d60142821a621d4d345b50622
  5. Compiled from "ArrayTest.java"
  6. public class cn.qlq.test.ArrayTest
  7. SourceFile: "ArrayTest.java"
  8. minor version: 0
  9. major version: 51
  10. flags: ACC_PUBLIC, ACC_SUPER
  11. Constant pool:
  12. #1 = Methodref #6.#15 // java/lang/Object."<init>":()V
  13. #2 = String #16 // abc
  14. #3 = Class #17 // java/lang/String
  15. #4 = Methodref #3.#18 // java/lang/String."<init>":(Ljava/lang/String;)V
  16. #5 = Class #19 // cn/qlq/test/ArrayTest
  17. #6 = Class #20 // java/lang/Object
  18. #7 = Utf8 <init>
  19. #8 = Utf8 ()V
  20. #9 = Utf8 Code
  21. #10 = Utf8 LineNumberTable
  22. #11 = Utf8 main
  23. #12 = Utf8 ([Ljava/lang/String;)V
  24. #13 = Utf8 SourceFile
  25. #14 = Utf8 ArrayTest.java
  26. #15 = NameAndType #7:#8 // "<init>":()V
  27. #16 = Utf8 abc
  28. #17 = Utf8 java/lang/String
  29. #18 = NameAndType #7:#21 // "<init>":(Ljava/lang/String;)V
  30. #19 = Utf8 cn/qlq/test/ArrayTest
  31. #20 = Utf8 java/lang/Object
  32. #21 = Utf8 (Ljava/lang/String;)V
  33. {
  34. public cn.qlq.test.ArrayTest();
  35. flags: ACC_PUBLIC
  36. Code:
  37. stack=1, locals=1, args_size=1
  38. 0: aload_0
  39. 1: invokespecial #1 // Method java/lang/Object."<init>":()V
  40. 4: return
  41. LineNumberTable:
  42. line 3: 0
  43.  
  44. public static void main(java.lang.String[]);
  45. flags: ACC_PUBLIC, ACC_STATIC
  46. Code:
  47. stack=3, locals=5, args_size=1
  48. 0: ldc #2 // String abc
  49. 2: astore_1
  50. 3: ldc #2 // String abc
  51. 5: astore_2
  52. 6: new #3 // class java/lang/String
  53. 9: dup
  54. 10: ldc #2 // String abc
  55. 12: invokespecial #4 // Method java/lang/String."<init>":(Ljava/lang/String;)V
  56. 15: astore_3
  57. 16: new #3 // class java/lang/String
  58. 19: dup
  59. 20: ldc #2 // String abc
  60. 22: invokespecial #4 // Method java/lang/String."<init>":(Ljava/lang/String;)V
  61. 25: astore 4
  62. 27: return
  63. LineNumberTable:
  64. line 5: 0
  65. line 6: 3
  66. line 7: 6
  67. line 8: 16
  68. line 9: 27
  69. }

上面的Constant pool:是所设计的常量信息,包括类名字、方法名字、字符串常量池信息信息。

下面就是编译之后的方法:

第一个构造方法研究:

  1. public cn.qlq.test.ArrayTest();
  2. flags: ACC_PUBLIC
  3. Code:
  4. stack=1, locals=1, args_size=1
  5. 0: aload_0
  6. 1: invokespecial #1 // Method java/lang/Object."<init>":()V
  7. 4: return
  8. LineNumberTable:
  9. line 3: 0

编译器给我们生成的无参构造方法,访问类型是public,

aload_0                     将第一个引用类型本地变量推送至栈顶(将this引用推送至栈顶,即压入栈。)

invokespecial #1                  // Method java/lang/Object."<init>":()V      调用超类构造方法,实例初始化方法,私有方法

return  函数结束(返回类型是void)

第二个main方法研究:

  1. public static void main(java.lang.String[]);
  2. flags: ACC_PUBLIC, ACC_STATIC
  3. Code:
  4. stack=3, locals=5, args_size=1
  5. 0: ldc #2 // String abc
  6. 2: astore_1
  7. 3: ldc #2 // String abc
  8. 5: astore_2
  9. 6: new #3 // class java/lang/String
  10. 9: dup
  11. 10: ldc #2 // String abc
  12. 12: invokespecial #4 // Method java/lang/String."<init>":(Ljava/lang/String;)V
  13. 15: astore_3
  14. 16: new #3 // class java/lang/String
  15. 19: dup
  16. 20: ldc #2 // String abc
  17. 22: invokespecial #4 // Method java/lang/String."<init>":(Ljava/lang/String;)V
  18. 25: astore 4
  19. 27: return
  20. LineNumberTable:
  21. line 5: 0
  22. line 6: 3
  23. line 7: 6
  24. line 8: 16
  25. line 9: 27

访问标志符号是static、public类型

ldc:  该系列命令负责把数值常量或String常量值从常量池中推送至栈顶。该命令后面需要给一个表示常量在常量池中位置(编号)的参数(#2代表上面标记为#2的常量)

astore_1                  将栈顶引用型数值存入指定第二个本地变量,超过3的格式变为  astore 4  此种格式

new  代表创建对象

invokespecial   代表调用方法

return   代表函数结束,返回类型是void

  然后对着命令自己去查去吧。。。。。。。。

2.拼接过程研究

1.第一种情况:

  1. package zd.dms.test;
  2.  
  3. public class ArrayTest {
  4.  
  5. public static void main(String[] args) {
  6. String s1 = "s1str";
  7. String s2 = "s2str";
  8. }
  9. }

这种情况与上面创建的情况一样,只是将"s1str"与"s2str"存到常量池中,从常量池中取出来之后加载到本地变量表(Class文件的一块结构)。

2.第二种情况:(详细且重要)

  1. package zd.dms.test;
  2.  
  3. public class ArrayTest {
  4.  
  5. public static void main(String[] args) {
  6. String s1 = "s1str";
  7. String s2 = "s2str";
  8. String s3 = s1 + s2;
  9. }
  10. }

反编译查看代码:

  1. C:\Users\Administrator\Desktop>javac ArrayTest.class
  2. javac: 无效的标记: ArrayTest.class
  3. 用法: javac <options> <source files>
  4. -help 用于列出可能的选项
  5.  
  6. C:\Users\Administrator\Desktop>javap -v -c ArrayTest.class
  7. Classfile /C:/Users/Administrator/Desktop/ArrayTest.class
  8. Last modified 2018-9-3; size 479 bytes
  9. MD5 checksum 383c1f53ced549e0dd7ba635ec9d6f57
  10. Compiled from "ArrayTest.java"
  11. public class zd.dms.test.ArrayTest
  12. SourceFile: "ArrayTest.java"
  13. minor version: 0
  14. major version: 51
  15. flags: ACC_PUBLIC, ACC_SUPER
  16. Constant pool:
  17. #1 = Methodref #9.#18 // java/lang/Object."<init>":()V
  18. #2 = String #19 // s1str
  19. #3 = String #20 // s2str
  20. #4 = Class #21 // java/lang/StringBuilder
  21. #5 = Methodref #4.#18 // java/lang/StringBuilder."<init>":()V
  22. #6 = Methodref #4.#22 // java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
  23. #7 = Methodref #4.#23 // java/lang/StringBuilder.toString:()Ljava/lang/String;
  24. #8 = Class #24 // zd/dms/test/ArrayTest
  25. #9 = Class #25 // java/lang/Object
  26. #10 = Utf8 <init>
  27. #11 = Utf8 ()V
  28. #12 = Utf8 Code
  29. #13 = Utf8 LineNumberTable
  30. #14 = Utf8 main
  31. #15 = Utf8 ([Ljava/lang/String;)V
  32. #16 = Utf8 SourceFile
  33. #17 = Utf8 ArrayTest.java
  34. #18 = NameAndType #10:#11 // "<init>":()V
  35. #19 = Utf8 s1str
  36. #20 = Utf8 s2str
  37. #21 = Utf8 java/lang/StringBuilder
  38. #22 = NameAndType #26:#27 // append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
  39. #23 = NameAndType #28:#29 // toString:()Ljava/lang/String;
  40. #24 = Utf8 zd/dms/test/ArrayTest
  41. #25 = Utf8 java/lang/Object
  42. #26 = Utf8 append
  43. #27 = Utf8 (Ljava/lang/String;)Ljava/lang/StringBuilder;
  44. #28 = Utf8 toString
  45. #29 = Utf8 ()Ljava/lang/String;
  46. {
  47. public zd.dms.test.ArrayTest();
  48. flags: ACC_PUBLIC
  49. Code:
  50. stack=1, locals=1, args_size=1
  51. 0: aload_0
  52. 1: invokespecial #1 // Method java/lang/Object."<init>":()V
  53. 4: return
  54. LineNumberTable:
  55. line 3: 0
  56.  
  57. public static void main(java.lang.String[]);
  58. flags: ACC_PUBLIC, ACC_STATIC
  59. Code:
  60. stack=2, locals=4, args_size=1
  61. 0: ldc #2 // String s1str ,把s1str推送到栈顶
  62. 2: astore_1                          ,把栈顶的值存放到第二个本地变量表
  63. 3: ldc #3 // String s2str  ,把上面#3常量池的数据推送到栈顶
  64. 5: astore_2                            ,栈顶数据存放到第三个本地变量表
  65. 6: new #4 // class java/lang/StringBuilder  ,创建一个StringBuilder对象并压入栈顶
  66. 9: dup                                         ,复制栈顶数据
  67. 10: invokespecial #5 // Method java/lang/StringBuilder."<init>":()V    调用超类构造方法,实例化初始方法,私有方法
  68. 13: aload_1                                           加载第二个本地变量表的数据
  69. 14: invokevirtual #6 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;  ,调用实例方法
  70. 17: aload_2                                            加载第三个本地变量表
  71. 18: invokevirtual #6 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;  ,调用实例方法
  72. 21: invokevirtual #7 // Method java/lang/StringBuilder.toString:()Ljava/lang/String;                ,调用实例方法
  73. 24: astore_3                                            存到第三四本地变量表
  74. 25: return                                              函数返回,返回类型是void
  75. LineNumberTable:
  76. line 6: 0
  77. line 7: 3
  78. line 8: 6
  79. line 9: 25
  80. }

解释:常量池只有"s1str"与"s2str"两个常量。

  当执行String s3 = s1 + s2;的时候是先创建StringBuilder并调用其append方法进行拼接,也就是当string拼接的时候如果有一个参数是引用,会创建StringBuilder,且拼接后的数据不会存放到常量池。

补充一点StringBuilder向后append的原理:

  1. package zd.dms.test;
  2.  
  3. public class ArrayTest {
  4.  
  5. public static void main(String[] args) {
  6. String s1 = "s1";
  7. StringBuilder sb = new StringBuilder();
  8. sb.append(s1);
  9. sb.append("s2");
  10. sb.append(s1 + "s2");// 参数相当于一个stringbuilder,之后转为string再次调用sb.append
  11. sb.append("s1" + "s2");// 相当于直接append("s1s2")
  12. }
  13.  
  14. }

  直接append字面量的时候会直接从常量池获取到字面量然后进行append,"a"+"b"被编译成"ab"存到常量池

   如果append的是引用+""字面量或者引用+引用的时候会将参数种的引用+"字面量"组合成一个stringbuilder,之后转为string再aoeend到原来的stringbuilder

反编译上面代码:

  1. C:\Users\Administrator\Desktop>javac ArrayTest.java
  2.  
  3. C:\Users\Administrator\Desktop>javap -v -c ArrayTest.class
  4. Classfile /C:/Users/Administrator/Desktop/ArrayTest.class
  5. Last modified 2018-9-3; size 525 bytes
  6. MD5 checksum ee9245ab7e1f9ac979b84f372b4a0c0f
  7. Compiled from "ArrayTest.java"
  8. public class zd.dms.test.ArrayTest
  9. SourceFile: "ArrayTest.java"
  10. minor version: 0
  11. major version: 51
  12. flags: ACC_PUBLIC, ACC_SUPER
  13. Constant pool:
  14. #1 = Methodref #10.#19 // java/lang/Object."<init>":()V
  15. #2 = String #20 // s1
  16. #3 = Class #21 // java/lang/StringBuilder
  17. #4 = Methodref #3.#19 // java/lang/StringBuilder."<init>":()V
  18. #5 = Methodref #3.#22 // java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
  19. #6 = String #23 // s2
  20. #7 = Methodref #3.#24 // java/lang/StringBuilder.toString:()Ljava/lang/String;
  21. #8 = String #25 // s1s2
  22. #9 = Class #26 // zd/dms/test/ArrayTest
  23. #10 = Class #27 // java/lang/Object
  24. #11 = Utf8 <init>
  25. #12 = Utf8 ()V
  26. #13 = Utf8 Code
  27. #14 = Utf8 LineNumberTable
  28. #15 = Utf8 main
  29. #16 = Utf8 ([Ljava/lang/String;)V
  30. #17 = Utf8 SourceFile
  31. #18 = Utf8 ArrayTest.java
  32. #19 = NameAndType #11:#12 // "<init>":()V
  33. #20 = Utf8 s1
  34. #21 = Utf8 java/lang/StringBuilder
  35. #22 = NameAndType #28:#29 // append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
  36. #23 = Utf8 s2
  37. #24 = NameAndType #30:#31 // toString:()Ljava/lang/String;
  38. #25 = Utf8 s1s2
  39. #26 = Utf8 zd/dms/test/ArrayTest
  40. #27 = Utf8 java/lang/Object
  41. #28 = Utf8 append
  42. #29 = Utf8 (Ljava/lang/String;)Ljava/lang/StringBuilder;
  43. #30 = Utf8 toString
  44. #31 = Utf8 ()Ljava/lang/String;
  45. {
  46. public zd.dms.test.ArrayTest();
  47. flags: ACC_PUBLIC
  48. Code:
  49. stack=1, locals=1, args_size=1
  50. 0: aload_0
  51. 1: invokespecial #1 // Method java/lang/Object."<init>":()V
  52. 4: return
  53. LineNumberTable:
  54. line 3: 0
  55.  
  56. public static void main(java.lang.String[]);
  57. flags: ACC_PUBLIC, ACC_STATIC
  58. Code:
  59. stack=3, locals=3, args_size=1
  60. 0: ldc #2 // String s1
  61. 2: astore_1
  62. 3: new #3 // class java/lang/StringBuilder
  63. 6: dup
  64. 7: invokespecial #4 // Method java/lang/StringBuilder."<init>":()V
  65. 10: astore_2
  66. 11: aload_2
  67. 12: aload_1
  68. 13: invokevirtual #5 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
  69. 16: pop
  70. 17: aload_2
  71. 18: ldc #6 // String s2
  72. 20: invokevirtual #5 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
  73. 23: pop
  74. 24: aload_2
  75. 25: new #3 // class java/lang/StringBuilder
  76. 28: dup
  77. 29: invokespecial #4 // Method java/lang/StringBuilder."<init>":()V
  78. 32: aload_1
  79. 33: invokevirtual #5 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
  80. 36: ldc #6 // String s2
  81. 38: invokevirtual #5 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
  82. 41: invokevirtual #7 // Method java/lang/StringBuilder.toString:()Ljava/lang/String;
  83. 44: invokevirtual #5 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
  84. 47: pop
  85. 48: aload_2
  86. 49: ldc #8 // String s1s2
  87. 51: invokevirtual #5 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
  88. 54: pop
  89. 55: return
  90. LineNumberTable:
  91. line 6: 0
  92. line 7: 3
  93. line 8: 11
  94. line 9: 17
  95. line 10: 24
  96. line 11: 48
  97. line 12: 55
  98. }

  

3.第三种情况:

  1. package zd.dms.test;
  2.  
  3. public class ArrayTest {
  4.  
  5. public static void main(String[] args) {
  6. String s1 = "s1str";
  7. String s2 = "s2str";
  8. String s3 = s1 + "s2str";
  9. String s4 = "s1str" + "s2str";
  10. }
  11. }

用反编译工具查看编译后代码:

  1. package zd.dms.test;
  2.  
  3. public class ArrayTest
  4. {
  5. public static void main(String[] paramArrayOfString)
  6. {
  7. String str1 = "s1str";
  8. String str2 = "s2str";
  9. String str3 = str1 + "s2str";
  10. String str4 = "s1strs2str";
  11. }
  12. }

对于全是字面量的字符串,Java编译的时候会拼接成一个字符串存放到常量池,例如str4

  只要有一个是引用就会创建StringBuilder进行append,例如str3

反编译验证:

  1. C:\Users\Administrator\Desktop>javap -v -c ArrayTest.class
  2. Classfile /C:/Users/Administrator/Desktop/ArrayTest.class
  3. Last modified 2018-9-3; size 504 bytes
  4. MD5 checksum 5c3826614f4bce5ff666591bdd2f8330
  5. Compiled from "ArrayTest.java"
  6. public class zd.dms.test.ArrayTest
  7. SourceFile: "ArrayTest.java"
  8. minor version: 0
  9. major version: 51
  10. flags: ACC_PUBLIC, ACC_SUPER
  11. Constant pool:
  12. #1 = Methodref #10.#19 // java/lang/Object."<init>":()V
  13. #2 = String #20 // s1str
  14. #3 = String #21 // s2str
  15. #4 = Class #22 // java/lang/StringBuilder
  16. #5 = Methodref #4.#19 // java/lang/StringBuilder."<init>":()V
  17. #6 = Methodref #4.#23 // java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
  18. #7 = Methodref #4.#24 // java/lang/StringBuilder.toString:()Ljava/lang/String;
  19. #8 = String #25 // s1strs2str
  20. #9 = Class #26 // zd/dms/test/ArrayTest
  21. #10 = Class #27 // java/lang/Object
  22. #11 = Utf8 <init>
  23. #12 = Utf8 ()V
  24. #13 = Utf8 Code
  25. #14 = Utf8 LineNumberTable
  26. #15 = Utf8 main
  27. #16 = Utf8 ([Ljava/lang/String;)V
  28. #17 = Utf8 SourceFile
  29. #18 = Utf8 ArrayTest.java
  30. #19 = NameAndType #11:#12 // "<init>":()V
  31. #20 = Utf8 s1str
  32. #21 = Utf8 s2str
  33. #22 = Utf8 java/lang/StringBuilder
  34. #23 = NameAndType #28:#29 // append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
  35. #24 = NameAndType #30:#31 // toString:()Ljava/lang/String;
  36. #25 = Utf8 s1strs2str
  37. #26 = Utf8 zd/dms/test/ArrayTest
  38. #27 = Utf8 java/lang/Object
  39. #28 = Utf8 append
  40. #29 = Utf8 (Ljava/lang/String;)Ljava/lang/StringBuilder;
  41. #30 = Utf8 toString
  42. #31 = Utf8 ()Ljava/lang/String;
  43. {
  44. public zd.dms.test.ArrayTest();
  45. flags: ACC_PUBLIC
  46. Code:
  47. stack=1, locals=1, args_size=1
  48. 0: aload_0
  49. 1: invokespecial #1 // Method java/lang/Object."<init>":()V
  50. 4: return
  51. LineNumberTable:
  52. line 3: 0
  53.  
  54. public static void main(java.lang.String[]);
  55. flags: ACC_PUBLIC, ACC_STATIC
  56. Code:
  57. stack=2, locals=5, args_size=1
  58. 0: ldc #2 // String s1str
  59. 2: astore_1
  60. 3: ldc #3 // String s2str
  61. 5: astore_2
  62. 6: new #4 // class java/lang/StringBuilder
  63. 9: dup
  64. 10: invokespecial #5 // Method java/lang/StringBuilder."<init>":()V
  65. 13: aload_1
  66. 14: invokevirtual #6 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
  67. 17: ldc #3 // String s2str
  68. 19: invokevirtual #6 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
  69. 22: invokevirtual #7 // Method java/lang/StringBuilder.toString:()Ljava/lang/String;
  70. 25: astore_3
  71. 26: ldc #8 // String s1strs2str
  72. 28: astore 4
  73. 30: return
  74. LineNumberTable:
  75. line 6: 0
  76. line 7: 3
  77. line 8: 6
  78. line 9: 26
  79. line 10: 30
  80. }

4.一种特殊情况

  1. package zd.dms.test;
  2.  
  3. public class ArrayTest {
  4.  
  5. public static void main(String[] args) {
  6. String s4 = "s1str" + "s2str";
  7. }
  8. }

  只会在常量池生成一个常量"s1strs2str",不会产生"s1str" 与 "s2str"常量

反编译工具查看:

  1. package zd.dms.test;
  2.  
  3. public class ArrayTest
  4. {
  5. public static void main(String[] paramArrayOfString)
  6. {
  7. String str = "s1strs2str";
  8. }
  9. }

javap查看:

  1. C:\Users\Administrator\Desktop>javap -v -c ArrayTest.class
  2. Classfile /C:/Users/Administrator/Desktop/ArrayTest.class
  3. Last modified 2018-9-3; size 298 bytes
  4. MD5 checksum c9ed3e29835a530b16285acbc84f4ea5
  5. Compiled from "ArrayTest.java"
  6. public class zd.dms.test.ArrayTest
  7. SourceFile: "ArrayTest.java"
  8. minor version: 0
  9. major version: 51
  10. flags: ACC_PUBLIC, ACC_SUPER
  11. Constant pool:
  12. #1 = Methodref #4.#13 // java/lang/Object."<init>":()V
  13. #2 = String #14 // s1strs2str
  14. #3 = Class #15 // zd/dms/test/ArrayTest
  15. #4 = Class #16 // java/lang/Object
  16. #5 = Utf8 <init>
  17. #6 = Utf8 ()V
  18. #7 = Utf8 Code
  19. #8 = Utf8 LineNumberTable
  20. #9 = Utf8 main
  21. #10 = Utf8 ([Ljava/lang/String;)V
  22. #11 = Utf8 SourceFile
  23. #12 = Utf8 ArrayTest.java
  24. #13 = NameAndType #5:#6 // "<init>":()V
  25. #14 = Utf8 s1strs2str
  26. #15 = Utf8 zd/dms/test/ArrayTest
  27. #16 = Utf8 java/lang/Object
  28. {
  29. public zd.dms.test.ArrayTest();
  30. flags: ACC_PUBLIC
  31. Code:
  32. stack=1, locals=1, args_size=1
  33. 0: aload_0
  34. 1: invokespecial #1 // Method java/lang/Object."<init>":()V
  35. 4: return
  36. LineNumberTable:
  37. line 3: 0
  38.  
  39. public static void main(java.lang.String[]);
  40. flags: ACC_PUBLIC, ACC_STATIC
  41. Code:
  42. stack=1, locals=2, args_size=1
  43. 0: ldc #2 // String s1strs2str
  44. 2: astore_1
  45. 3: return
  46. LineNumberTable:
  47. line 6: 0
  48. line 7: 3
  49. }

2.Intern()方法详解

intern()有两个作用,第一个是将字符串字面量放入常量池(如果池没有的话),第二个是返回这个常量的引用。

  intern中说的“如果有的话就直接返回其引用”,指的是会把字面量对象的引用直接返回给定义的对象。这个过程是不会在Java堆中再创建一个String对象的。

  1. public native String intern();

一个例子:

  1. package zd.dms.test;
  2.  
  3. public class ArrayTest {
  4.  
  5. public static void main(String[] args) {
  6. String s1 = "Hollis";
  7. String s2 = new String("Hollis");
  8. String s3 = new String("Hollis").intern();
  9.  
  10. System.out.println(s1 == s2);
  11. System.out.println(s1 == s3);
  12. }
  13. }

结果:

false
true

  可以简单的理解为String s1 = "Hollis";String s3 = new String("Hollis").intern();做的事情是一样的(但实际有些区别)。都是定义一个字符串对象,然后将其字符串字面量保存在常量池中,并把这个字面量的引用返回给定义好的对象引用。如下图:

  对于String s3 = new String("Hollis").intern();,在不调intern情况,s3指向的是JVM在堆中创建的那个对象的引用的(如图中的s2)。但是当执行了intern方法时,s3将指向字符串常量池中的那个字符串常量。

  由于s1和s3都是字符串常量池中的字面量的引用,所以s1==s3。但是,s2的引用是堆中的对象,所以s2!=s1。

intern的正确用法(*)

我们知道在String s3 = new String("Hollis").intern();中,其实intern是多余的?

  因为就算不用intern,Hollis作为一个字面量也会被加载到Class文件的常量池,进而加入到运行时常量池中,为啥还要多此一举呢?到底什么场景下才会用到intern呢?

常量池要保存的是已确定的字面量值。也就是说,对于字符串的拼接,纯字面量和字面量的拼接,会把拼接结果作为常量保存到字符串。

如果在字符串拼接中,有一个参数是非字面量,而是一个变量的话,整个拼接操作会被编译成StringBuilder.append,这种情况编译器是无法知道其确定值的。只有在运行期才能确定。

那么,有了这个特性了,intern就有用武之地了。那就是很多时候,我们在程序中用到的字符串是只有在运行期才能确定的,在编译期是无法确定的,那么也就没办法在编译期被加入到常量池中。

这时候,对于那种可能经常使用的字符串,使用intern进行定义,每次JVM运行到这段代码的时候,就会直接把常量池中该字面值的引用返回,这样就可以减少大量字符串对象的创建了。

如一美团点评团队的《深入解析String#intern》文中举的一个例子:

  1. static final int MAX = 1000 * 10000;
  2. static final String[] arr = new String[MAX];
  3.  
  4. public static void main(String[] args) throws Exception {
  5. Integer[] DB_DATA = new Integer[10];
  6. Random random = new Random(10 * 10000);
  7. for (int i = 0; i < DB_DATA.length; i++) {
  8. DB_DATA[i] = random.nextInt();
  9. }
  10. for (int i = 0; i < MAX; i++) {
  11. arr[i] = new String(String.valueOf(DB_DATA[i % DB_DATA.length])).intern();
  12. }
  13. }

  在以上代码中,我们明确的知道,会有很多重复的相同的字符串产生,但是这些字符串的值都是只有在运行期才能确定的。所以,只能我们通过intern显示的将其加入常量池,这样可以减少很多字符串的重复创建

3.equals()和hashCode方法详解

hashCode()源码查看:

  1. public int hashCode() {
  2. int h = hash;//默认为0
  3. if (h == 0 && value.length > 0) {
  4. char val[] = value;
  5.  
  6. for (int i = 0; i < value.length; i++) {
  7. h = 31 * h + val[i];
  8. }
  9. hash = h;
  10. }
  11. return h;
  12. } 

看出来String是遍历每个char,h乘以31加上对应char的ASCII码。

验证:

  1. String s1 = "a";
  2. String s2 = "b";
  3. System.out.println(s1.hashCode());//97
  4. System.out.println(s2.hashCode());//98

我们利用两个hashCode相等的字符串作为key存入map,查看:

  1. package cn.qlq.test;
  2.  
  3. import java.util.HashMap;
  4.  
  5. public class ArrayTest {
  6. public static void main(String[] args) {
  7. String s1 = "Aa";
  8. String s2 = "BB";
  9. System.out.println(s1.hashCode());
  10. System.out.println(s2.hashCode());
  11.  
  12. HashMap map = new HashMap();
  13. map.put(s1, "xxx");
  14. map.put(s2, "xxxdddd");
  15. System.out.println(map);
  16. }
  17.  
  18. }

2112
2112
{BB=xxxdddd, Aa=xxx}

"Aa" 与"BB"的hashCode相等,那么是如何存入map的?--验证hashmap的实现原理基于数据+链表

     

  先存入Aa,并放在第五个数组位置,当存BB的时候发现hashCode一样,会将BB存到第五个位置,并将第五个位置元素的next(也是一个Entry)存为Aa。也就是数组加链表实现原理

equals(obj)源码查看:  是将形参转变为String,然后遍历里面的char[],两个char[]进行依次对比。也就是比较字符串的值是否相等,这个也很常用。

  1. public boolean equals(Object anObject) {
  2. if (this == anObject) {
  3. return true;
  4. }
  5. if (anObject instanceof String) {
  6. String anotherString = (String) anObject;
  7. int n = value.length;
  8. if (n == anotherString.value.length) {
  9. char v1[] = value;
  10. char v2[] = anotherString.value;
  11. int i = 0;
  12. while (n-- != 0) {
  13. if (v1[i] != v2[i])
  14. return false;
  15. i++;
  16. }
  17. return true;
  18. }
  19. }
  20. return false;
  21. }

4..String引用传值图解(由于是不可变类,所以给形参赋值的时候相当于新建对象,不会影响实参)

更进一步的理解:"引用传值也是按值传递,只不过传的是对象的地址"。

比如下面一段代码:

  1. package cn.qlq.test;
  2.  
  3. import java.util.Arrays;
  4.  
  5. public class ArrayTest {
  6. public static void main(String[] args) {
  7. String s = "hello";
  8. test(s);
  9. System.out.println(s);
  10. }
  11.  
  12. public static void test(String s1) {
  13. s1 = "world";
  14. }
  15. }

结果:

hello

解释:调用test方法的时候采用引用传递(将s的地址传下去),执行s1="world"是新创一个"world"并赋值给s1,也就是s1此时已经指向其他对象,不再与s指向相同对象。

图解:

  

补充:str+str2是通过StringBuilder的append方式实现的,而且当String为null的时候append方法会转为null串。

记住一句话:String进行 + 拼接的时候永远不会报错,遇到null也会生成null串处理

  1. package cn.xm.exam.test;
  2.  
  3. public class test {
  4. public static void main(String[] args) {
  5. String s1 = "s1";
  6. String s2 = null;
  7. String s3 = s1+s2;
  8. System.out.println(s3);
  9. }
  10. }

结果:

s1null

反汇编上面的代码:

  1. public static void main(java.lang.String[]);
  2. flags: ACC_PUBLIC, ACC_STATIC
  3. LineNumberTable:
  4. line 3: 0
  5. line 4: 3
  6. line 5: 5
  7. line 6: 24
  8. line 7: 31
  9. Code:
  10. stack=2, locals=4, args_size=1
  11. 0: ldc #2 // String s1
  12. 2: astore_1
  13. 3: aconst_null
  14. 4: astore_2
  15. 5: new #3 // class java/lang/StringBuilder
  16. 8: dup
  17. 9: invokespecial #4 // Method java/lang/StringBuilder."<init>":()V
  18. 12: aload_1
  19. 13: invokevirtual #5 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
  20. 16: aload_2
  21. 17: invokevirtual #5 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
  22. 20: invokevirtual #6 // Method java/lang/StringBuilder.toString:()Ljava/lang/String;
  23. 23: astore_3
  24. 24: getstatic #7 // Field java/lang/System.out:Ljava/io/PrintStream;
  25. 27: aload_3
  26. 28: invokevirtual #8 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
  27. 31: return

查看StringBuilder的append()方法的源码:发现确实传入null的时候转为null串了

  1. public StringBuilder append(String str) {
  2. super.append(str);
  3. return this;
  4. }
  1. public AbstractStringBuilder append(String str) {
  2. if (str == null) str = "null";
  3. int len = str.length();
  4. ensureCapacityInternal(count + len);
  5. str.getChars(0, len, value, count);
  6. count += len;
  7. return this;
  8. }

String类的深入理解的更多相关文章

  1. 从C# String类理解Unicode(UTF8/UTF16)

    上一篇博客:从字节理解Unicode(UTF8/UTF16).这次我将从C# code 中再一次阐述上篇博客的内容. C# 代码看UTF8 代码如下: string test = "UTF- ...

  2. 跟着刚哥梳理java知识点——深入理解String类(九)

    一.String类 想要了解一个类,最好的办法就是看这个类的实现源代码,来看一下String类的源码: public final class String implements java.io.Ser ...

  3. 深入理解Java String类(综合)

    在Java语言了中,所有类似“ABC”的字面值,都是String类的实例:String类位于java.lang包下,是Java语言的核心类,提供了字符串的比较.查找.截取.大小写转换等操作:Java语 ...

  4. 深入理解String类详解

    1.Stringstr = "eee" 和String str = new String("eee")的区别 先看一小段代码, 1 public static ...

  5. 深入理解String类

    1.String str = "eee" 和String str = new String("eee")的区别 先看一小段代码, public static v ...

  6. Java基础系列2:深入理解String类

    Java基础系列2:深入理解String类 String是Java中最为常用的数据类型之一,也是面试中比较常被问到的基础知识点,本篇就聊聊Java中的String.主要包括如下的五个内容: Strin ...

  7. String Buffer和String Builder(String类深入理解)

      String在Java里面JDK1.8后它属于一个特殊的类,在创建一个String基本对象的时候,String会向“ 字符串常量池(String constant pool)” 进行检索是否有该数 ...

  8. jdk源码理解-String类

    String类的理解 简记录一下对于jdk的学习,做一下记录,会持续补充,不断学习,加油 1.String的hash值的计算方法. hash值的计算方法多种多样,jdk中String的计算方法如下,比 ...

  9. #String类简述(小白理解,小白编写,欢迎大神指点,小白跪谢)

    @ 目录 一.前言(可忽略) 二.String变量的认知 三.String类的构造方法 四.String类的基本方法 4.1 toString()方法 4.2 equals()方法 4.3 equal ...

随机推荐

  1. VS2015 C#的单元测试

    1.安装visual studio 2015过程 visual studio 会对windows系统兼容性有很高的要求,没有达到win7 sp1以上的就不给安装,贴一张官方的系统的要求吧. 很不幸的是 ...

  2. 从零开始学Kotlin-操作符(3)

    从零开始学Kotlin基础篇系列文章 冒号操作符 ":" 和 "::" :操作符用来定义变量.类的继承等 var name: String//定义变量 clas ...

  3. “耐撕”团队 2016.03.25 站立会议

    1.时间:2016.3.23  2.成员: Z 郑蕊 * 组长 (博客:http://www.cnblogs.com/zhengrui0452/), P 濮成林(博客:http://www.cnblo ...

  4. html+css照片墙

    html文件 <!DOCTYPE html><html lang="en"><head> <meta charset="UTF- ...

  5. Node.js & SSR

    Node.js & SSR nest.js https://github.com/nestjs/nest next.js 中文文档 https://nextjs.org/learn/ Grap ...

  6. 一本通1641【例 1】矩阵 A×B

    1641: [例 1]矩阵 A×B sol:矩阵乘法模板.三个for循环 #include <bits/stdc++.h> using namespace std; typedef lon ...

  7. poj3667 Hotel (线段树 区间合并)

    poj3667 HotelTime Limit: 3000MS Memory Limit: 65536KTotal Submissions: 18925 Accepted: 8242Descripti ...

  8. Spring Shell参考文档

    Spring Shell的核心组件是它的插件模型(plugin model).内置命令(built-in commands)和转换器( converters). 1.1 Plugin Model(插件 ...

  9. 洛谷P3275 [SCOI2011]糖果(差分约束,最长路,Tarjan,拓扑排序)

    洛谷题目传送门 差分约束模板题,等于双向连0边,小于等于单向连0边,小于单向连1边,我太蒻了,总喜欢正边权跑最长路...... 看遍了讨论版,我是真的不敢再入复杂度有点超级伪的SPFA的坑了 为了保证 ...

  10. 【BZOJ2178】圆的面积并(辛普森积分)

    [BZOJ2178]圆的面积并(辛普森积分) 题面 BZOJ 权限题 题解 把\(f(x)\)设为\(x\)和所有圆交的线段的并的和. 然后直接上自适应辛普森积分. 我精度死活一个点过不去,不要在意我 ...