JAVA中String 是Final类不能被继承。JAVA 对String的处理和一般Class有所不同。
这文章主要是解释一下String的存储模式和java的字符串常量池的机制,和几个涉及底层的引用问题解析。
首先提出几个问题:
1.String的内容为什么是不可更改的?
2.JAVA中“adc”这种创建的字符串的创建过程是怎样的?
3.String(String string)的构造方法是如何工作的?
4.一个线程中内容为“adc”的String对象,存储的char[]是否是同一个,char[]数组是否一定在字符串常量池中?
5思考java中String 不可更改的好处在哪?
6 intern方法和字符串常量池的关系?
7string的+编译器是如何处理的?
1.String的内容为什么是不可更改的?
我们通过源代码可以看到存储string内容的char[]是这么定义的:
private final char value[];
可能有人会有疑问既然是final引用却没有附初始值。
答案是final变量是可以在构造方法中进行赋值的。
所以value的所有赋值都在String的几个构造方法中。
这样从代码逻辑上控制了String不可变。
2.JAVA中“adc”这种创建的字符串的创建过程是怎样的?
这个问题比较简单,就是”adc“会被放到字符串常量池中,可以称为字面量,所有String s=“adb” 的字符串的引用都是指向字符串常量池中的。
3.String(String string)的构造方法是如何工作的?
public String(String original) {
this.value = original.value;
this.hash = original.hash;
}
这说明从用String去new String构造新的对象是,确实会产生新的对象,而且新的value和hash值都和原String对象一致。
当然String还是有其他构造方法,都会去new char[]
4一个线程中内容为“adc”的String对象,存储的char[]是否是同一个,char[]数组是否一定在字符串常量池中?
答案其实可以根据3推导出。
由String产生的String里面的char[]是同一个,其他方式产生的都是新的。“”包裹产生的字符串会在常量池中,其他的都是正常的存在堆中。所以堆中可以有n份“adb”的串,常量池中的“adc”永远只有一个,可以被多个引用所指向。后续将会用代码解释上述所有现象。
public static void main(String[] args)
throws NoSuchFieldException, SecurityException, IllegalArgumentException, IllegalAccessException {
// TODO Auto-generated method stub
String string = "abc";
String string2 = "abc";
String string3 = new String(string);
char[] charString={'a','b','c'};
String string4 = new String(charString);
String string5 = new String(string4);
charString[2]='e';
Class sClass = String.class;
Field privateStringField = sClass.getDeclaredField("value");
privateStringField.setAccessible(true);
char[] chars = (char[]) privateStringField.get(string);
char[] chars2 = (char[]) privateStringField.get(string2);
char[] chars3 = (char[]) privateStringField.get(string3);
char[] chars4 = (char[]) privateStringField.get(string4);
char[] chars5 = (char[]) privateStringField.get(string5);
System.out.println("++" + chars + " " + Arrays.toString(chars));
System.out.println("++" + chars2 + " " + Arrays.toString(chars2));
System.out.println("++" + chars3 + " " + Arrays.toString(chars3));
System.out.println("++" + chars4 + " " + Arrays.toString(chars4));
System.out.println("++" + chars5 + " " + Arrays.toString(chars5));
System.out.println("++" + charString + " " + Arrays.toString(charString));
System.out.println(string==string2);
System.out.println(string3==string);
System.out.println(string4==string);
System.out.println(string5==string4);
}
控制台输出:
++[C@4e25154f [a, b, c]
++[C@4e25154f [a, b, c]
++[C@4e25154f [a, b, c]
++[C@70dea4e [a, b, c]
++[C@70dea4e [a, b, c]
++[C@5c647e05 [a, b, e]
true
false
false
false
我们用反射可以拿到char[] 的引用,并打印出char[] 指向的内存地址。
可见前三个String都是指向同一个地址的(字符串常量池),后三个String都是指向堆中的地址。
5思考java中String 不可更改的好处在哪?
1.如果可变复用将变得不稳定。复用可以节约内存
2.hashcode被缓存,没必要重复结算
3.线程安全(网上的说法,我并不完全赞同,能保证值得安全,并不能保证引用的安全总是)
4.如果定义为final 也就是String的引用和内容都会稳定不可变(当然不包括使用反射的情况)
6intern方法的作用?
intern的作用是 将string放入常量池,并返回该引用,如果已经在常量池中直接返回引用。
在JDK1.6之后 intern方法是将不存在字符串常量池的String去记录到常量池里(存的是引用)实例还是在堆上
在JDK1.6之前intern方法是把String复制到字符串常量池里是新对象了(据说是在永久代中)目前可以确定String实例肯定不是一个了,里面的char[]是否复用,这个还不确定,手里没有JDK16的环境,可以用我上面的反射方法来进行验证。
7string的+编译器是如何处理的?
两种情况:
如果只是“” +“”+“”+… 不涉及变量的,javac会直接合并所有的"" “” ,形成utf8 结构体,保存下来,结构体中有个2byte的length字段记录字面量(”“这种东西官方称为字面量)在类运行中会把结构体的东西加载到内存的字符串常量池中(具体加载细节后续再聊)
第二种情况:
String j1="a";
String j2="b";
String j12="ab";
String j3="a"+j12;
System.out.println(j3 == j12); (输出是false)
遇到这种情况javac并不会合并”“ ”“,尽管j12也是字面量的变量。我们可以通过javap -c查看字节码:
Code:
0: ldc #2 // String a
2: astore_2
3: ldc #3 // String b
5: astore_3
6: new #4 // class java/lang/StringBuilder
9: dup
10: invokespecial #5 // Method java/lang/StringBuilder."":()V
13: ldc #2 // String a
15: invokevirtual #6 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
18: aload_3
19: invokevirtual #6 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
22: invokevirtual #7 // Method java/lang/StringBuilder.toString:()Ljava/lang/String;
25: astore 4
很清楚的看到,这里会new StringBuilder 调用append方法,最后toString。StringBulider的toString方法我们可以看一下,是根据当前的char[]去 newString的,所以必然不是用一个对象,所以输出的结果是false。
未完待续…