前言
提到 Java
的String
,都会提起 String
是不可变的。但是这点是真的吗?String
的不可变是否可以破坏呢?
在验证之前,首先还是需要介绍一下 String
的不可变特性。
PS:这里还要提到自己遇到的面试题:
String str = "abc";
// 输出 def
System.out.println(str);
String 不可变特性
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
提供的特性,那么被人拿来使用就很难避免。
当时遇到前面提到的面试题的时候,还一直认为此题无解,但是随着自己不断学习后才发现,很多时候换个角度就能发现不同的办法。