String不可变字符串
java中的String是不可变字符串,这个是官方发布的身份证明。
Strings are constant; their values cannot be changed after they are created.
字符串是常量,他们的值在创建后不能被更改。
我们可以通过JDK的String源码中看到相关的表现
public final class String
implements java.io.Serializable, Comparable<String>, CharSequence {
/** The value is used for character storage. 该值用于字符存储*/
private final char value[];
}
我们可以看到
- String的底层存储字符串使用的是char[]存储
- char[] value 是被private final,修饰的表示不可变的私有变量。但是我们需要注意的是,是value指向的char数组的地址不可变,但是其内容是可变的。
- String中没有提供直接获得char[] value 的方法。所以String的value属性对我们是不可见的。
以上3点可以解释为什么String是不可变字符串。
但是我们真的没法改变String的值吗?
答案是可以改变的。
思路:
我们已经知道String的底层是char[] 。如果我们获得value的char[] 数组,直接修改value数组中的内容就可以了。那如何获得类中的私有属性呢?反射。
String s1 = " abcdefg ";
//获得String的 value属性
Field value = String.class.getDeclaredField("value");
//开启私有属性和方法的可读模式
value.setAccessible(true);
//获得s1 对象的 value值
char[] o = (char[]) value.get(s1);
o[0] = 's';
System.out.println(s1);
/**
* sabcdefg
**/
但是这样使用是很有风险的。
String s1 = " abcdefg ";
String s2 = " abcdefg ";
Field value = String.class.getDeclaredField("value");
value.setAccessible(true);
char[] o = (char[]) value.get(s1);
o[0] = 's';
System.out.println(s1);
System.out.println(s2);
/**
* sabcdefg
* sabcdefg
**/
我们知道s2我们并没有进行修改,但是输出是s2的值也发生了改变,那是因为 s1和s2的char[] 指向的地址是同一个地址。因为String声明定义是我们会现在字符串常量池(JDK1.8之后,在元空间)中常见其字符串,然后堆中对象指向该字符串。下一个String对象的值如果相同则其值也会指向该字符串。
还会发生更严重的错误:
String s1 = " abcdefg ";
String s2 = " abcdefg ";
Field value = String.class.getDeclaredField("value");
value.setAccessible(true);
char[] o = (char[]) value.get(s1);
o[0] = 's';
System.out.println(s1);
System.out.println(s2);
String s3 = " abcdefg ";
System.out.println(s3);
System.out.println(" abcdefg ");
/**
* sabcdefg
* sabcdefg
* sabcdefg
* sabcdefg
**/
我们会发现" abcdefg “的输出竟然是"sabcdefg”.这这这……。
因为字符串常量池中由对应的索引,所以" abcdefg “的索引对应的内容是"sabcdefg”,打印时打印的是内容