java中的String的不可變性,網上已經有了大把大把的文章證實了。通常都是都是經過代碼證實的,固然,我也不能免俗,我先列一段代碼:java
public static void main(String[]args){
String one=new String("abc");
String two=new String("abc");
System.out.println("第一步one==two:"+(one==two));
System.out.println("第二步one.equals(two):"+one.equals(two));
System.out.println();
String three=one;
System.out.println("第三步one==three:"+(one==three));
System.out.println("第四步one.equals(three):"+one.equals(three));
System.out.println();
one="abcdefg";
System.out.println("第五步one==three:"+(one==three));
System.out.println("第六步one.equals(three):"+one.equals(three));
System.out.println();
String seven="abc";
String eight="abc";
System.out.println("第七步seven==eight:"+(seven==eight));
System.out.println("第八步seven.equals(eight):"+seven.equals(eight));
}
複製代碼
答案爲:數組
第一步one==two:false
第二步one.equals(two):true
第三步one==three:true
第四步one.equals(three):true
第五步one==three:false
第六步one.equals(three):false
第七步seven==eight:true
第八步seven.equals(eight):true
複製代碼
咱們一步一步來看代碼。spa
第一步:第一步的答案是爲false,這固然是沒有疑問的,是new出的兩個不一樣對象,經過「==」來比較的話,比較的是兩個對象之間的引用,固然是不相同的,因此爲false。code
第二步:第二步的答案是true。這是對的,雖然是兩個不一樣的對象,可是兩個對象引用指向的值都是同樣的,而經過「equals」來進行比較的話,比較的是兩個對象的引用所指向的值,因此爲true。cdn
第三步,第四步:第三步和第四步的答案都爲true。在看第三步以前,咱們要看到以前有一個代碼:對象
String three=one;
複製代碼
這個代碼,意味着將one這個字符串的引用值賦給three,也就是說,one和three指向的是同一個對象,那麼第三步和第四步的值固然都爲true了。blog
第五步,第六步:到了這一步以前,先看以前的代碼:three
String one=new String("abc");
String three=one;
one="abcdefg";
System.out.println("第五步one==three:"+(one==three));
System.out.println("第六步one.equals(three):"+one.equals(three));
複製代碼
我把以前的和第五步和第六步相關的代碼提煉出來。由於剛開始one和three是指向的同一個對象,可是後面one又改變了,one="abcdefg",因而得出的第五步和第六步的值都是false。這是爲何呢?字符串
若是one和three指向的都是同一個對象,那麼對one的修改應該是徹底同步到three上面纔對啊?get
其實,在one=「abcdefg」這段代碼,由於在如今的jdk版本中,String常量池的存在於堆中,當發如今常量池裏面沒有「abcdefg」這個字符串,那麼就會生成一個新的字符串對象。 那麼,咱們就能理解另外爲何第五步和第六步的值都爲false了,由於one已經其實是一個新new出來的對象,和three是徹底不一樣的兩個對象了。
這個地方,咱們引入的正是java中的String的不可變性。
第七步,第八步:到這一步,咱們先把代碼提煉出來。
String seven="abc";
String eight="abc";
System.out.println("第七步seven==eight:"+(seven==eight));
System.out.println("第八步seven.equals(eight):"+seven.equals(eight));
複製代碼
答案都是true,咱們難免產生了迷惑,這和第五步,第六步說的彷佛不同啊?這個時候咱們深刻的去了解一下在java中,String類型的究竟是怎麼生成對象的。
先說new String("abc")的方法建立的字符串,這種方法是無論何時,都是new一個對象出來。
而另一種是String seven="abc";這種類型的,這種類型的方法呢,先在棧中建立一個對String類的對象引用變量str,而後查找棧中(也是咱們所說的字符串常量池,jdk版本爲1.6及以前,常量池是存在Pern Gen區,也就是方法區。1.7版本後常量池就存在與堆中了)有沒有存放"abc",若是沒有,則將"abc"存放進棧,並令str指向」abc」,若是已經有」abc」 則直接令str指向「abc」。 也就是說,這種方法,可能不會new一個對象出來,可能只是指向了同一個引用而已。
這樣的話,咱們便能理解第五步第六步,也能理解第七步和第八步了。seven是生成了一個新的對象,可是eight並無生成一個新的對象,它只是在棧中發現了它須要的並且是由seven生成的「abc」,因而它便直接指向了這個對象。
固然,僅僅經過咱們的代碼來驗證,仍是不夠的,一般咱們須要去了解一下String類型的源碼,才能更加深入的理解java中的String的不可變這個特性。
String類型的源碼以下:
第一:String類型的長度是不可改變的。(由於底層是數組)
第二:String類型的值是不可改變的。(由於是final修飾的)
複製代碼
固然,實際上在java中由於反射的緣由,咱們能夠對String類型的值進行修改,真正能堅持不變的多是String類型的長度。
經過反射修改String類型的值的代碼以下:
static final Unsafe unsafe = getUnsafe();
static final boolean is64bit = true;
public static void main(String[]args) throws NoSuchFieldException, IllegalAccessException {
String s = "Hello World";
Double[] ascending = new Double[16];
for(int i=0;i<ascending.length;i++){
ascending[i] = (double) i;
}
System.out.println("未經過反射修改字符串的值:");
printAddresses(s, ascending);
//獲取String類中的value屬性
Field valueField = String.class.getDeclaredField("value");
//改變value屬性的訪問權限
valueField.setAccessible(true);
//獲取s對象上的value屬性的值
char[] value = (char[]) valueField.get(s);
//改變value所引用的數組中的第6個字符
value[5] = '_';
System.out.println("經過反射修改字符串的值:");
printAddresses(s, ascending);
}
/**
* 獲取對象的引用值
* @param label
* @param objects
*/
public static void printAddresses(String label, Object... objects) {
System.out.print("s="+label + " 引用值爲: 0x");
long last = 0;
int offset = unsafe.arrayBaseOffset(objects.getClass());
int scale = unsafe.arrayIndexScale(objects.getClass());
switch (scale) {
case 4:
long factor = is64bit ? 8 : 1;
final long i1 = (unsafe.getInt(objects, offset) & 0xFFFFFFFFL) * factor;
System.out.print(Long.toHexString(i1));
for (int i = 1; i < objects.length; i++) {
final long i2 = (unsafe.getInt(objects, offset + i * 4) & 0xFFFFFFFFL) * factor;
}
break;
case 8:
throw new AssertionError("Not supported");
}
System.out.println();
}
private static Unsafe getUnsafe() {
try {
Field theUnsafe = Unsafe.class.getDeclaredField("theUnsafe");
theUnsafe.setAccessible(true);
return (Unsafe) theUnsafe.get(null);
} catch (Exception e) {
throw new AssertionError(e);
}
}
複製代碼
打印出的結果爲:
未經過反射修改字符串的值:
s=Hello World 引用值爲: 0x76be43a18
經過反射修改字符串的值:
s=Hello_World 引用值爲: 0x76be43a18
複製代碼
咱們發現,對象的值變了,可是引用沒有變。
因此,實際上,String類型的不可變,是長度的不可變,它的值確實是能夠經過反射進行改變的。