提到 Java
的 String
,都会提起 String
是不可变的。但是这点是真的吗? String
的不可变是否可以破坏呢?
在验证之前,首先还是需要介绍一下 String
的不可变特性。
PS:这里还要提到自己遇到的面试题:
String str = "abc"; // 输出def System.out.println(str);
String
的不可变指的是
String
内部是使用一个被 final
修饰 char
数组 value
存储字符串的值 value
的值在对象构造的时候就已经进行了赋值 String
不提供方法对数组 value
中的值进行修改 String
中需要对 value
进行修改的方法(例如 replace
)则是直接返回一个新的 String
对象
所以 String
是不可变的。
String
的不可变
String
的不可变其实主要是围绕 value
是一个值不可修改的 char
数组来实现的,但是利用 Java
的反射完全可以破坏这个特性。
关键代码如下:
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对象的地址 的示例代码,内容如下:
/** * 打印对象地址 * @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
提供的特性,那么被人拿来使用就很难避免。
当时遇到前面提到的面试题的时候,还一直认为此题无解,但是随着自己不断学习后才发现,很多时候换个角度就能发现不同的办法。