提到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
当时遇到前面提到的面试题的时候,还一直认为此题无解,可是随着本身不断学习后才发现,不少时候换个角度就能发现不一样的办法。