提到Java
的String
,都會提起String
是不可變的。可是這點是真的嗎?String
的不可變是否能夠破壞呢?面試
在驗證以前,首先仍是須要介紹一下String
的不可變特性。數組
PS:這裏還要提到本身遇到的面試題:ide
String str = "abc"; // 輸出def System.out.println(str);
String
的不可變指的是學習
String
內部是使用一個被final
修飾char
數組value
存儲字符串的值value
的值在對象構造的時候就已經進行了賦值String
不提供方法對數組value
中的值進行修改String
中須要對value
進行修改的方法(例如replace
)則是直接返回一個新的String
對象因此String
是不可變的。.net
String
的不可變String
的不可變其實主要是圍繞value
是一個值不可修改的char
數組來實現的,可是利用Java
的反射徹底能夠破壞這個特性。code
關鍵代碼以下:對象
String str="test"; // str對象的引用地址 printAddresses("str",str); try { Field field=str.getClass().getDeclaredField("value"); // char[] newChars={'h','e','l','l','o'}; // 使private屬性的值能夠被訪問 field.setAccessible(true); // 替換value數組的值 field.set(str,newChars); // 替換後的值 System.out.println("str value:"+str); // 替換後,str對象的引用地址 printAddresses("str",str); } catch (NoSuchFieldException e) { e.printStackTrace(); } catch (IllegalAccessException e) { e.printStackTrace(); }
上面打印對象地址的方法是引用了如何獲取到Java對象的地址的示例代碼(本來是準備使用 System.identityHashCode()
方法),內容以下:blog
/** * 打印對象地址 * @param label * @param objects */ public void printAddresses(String label, Object... objects) { System.out.print(label + ":0x"); long last = 0; Unsafe unsafe=getUnsafe(); int offset = unsafe.arrayBaseOffset(objects.getClass()); int scale = unsafe.arrayIndexScale(objects.getClass()); switch (scale) { case 4: // 64位JVM long factor = 8; final long i1 = (unsafe.getInt(objects, offset) & 0xFFFFFFFFL) * factor; System.out.print(Long.toHexString(i1)); last = i1; for (int i = 1; i < objects.length; i++) { final long i2 = (unsafe.getInt(objects, offset + i * 4) & 0xFFFFFFFFL) * factor; if (i2 > last) System.out.print(", +" + Long.toHexString(i2 - last)); else System.out.print(", -" + Long.toHexString(last - i2)); last = i2; } break; case 8: throw new AssertionError("Not supported"); } System.out.println(); } /** * 經過反射獲取Unsafe對象 * @return */ 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); } }
str:0x76b4136e0 str value:hello str:0x76b4136e0
從運行結果能夠得知,使用反射能夠對String
對象的值進行修改,同時不會修改這個對象的對象地址。字符串
其實使用反射來破壞String
的不可變存在取巧成分,可是實際上反射也是Java
提供的特性,那麼被人拿來使用就很難避免。get
當時遇到前面提到的面試題的時候,還一直認爲此題無解,可是隨着本身不斷學習後才發現,不少時候換個角度就能發現不一樣的辦法。