介绍
文章写得有点杂,有对常量池的理解和字符串的值通过反射修改!!
开始讲解
先准备一个基础类:
public class A {
public final String tempString="wide world";
public String getTempString() {
return tempString;
}
}
public class TestA {
public static void main(String[] args) throws IllegalArgumentException, IllegalAccessException, InvocationTargetException {
A a1=new A();
A a2=new A();
System.out.println("判断a1.tempString与a2.tempString是否相同!");
System.out.println(a1.tempString==a2.tempString);
System.out.println("########################");
System.out.println("判读a1.tempString的值与\"wide world\"是否相同");
System.out.println(a1.tempString=="wide world");
System.out.println("#####################");
Field tempStringField[]=a1.tempString.getClass().getDeclaredFields();
for(Field i:tempStringField){
if(i.getName().equals("value")){
i.setAccessible(true);
System.out.println("通过反射获取值字符串里面的值");
char[] tempStringCharArrayValue=(char[]) i.get(a1.tempString);
for(char temp:tempStringCharArrayValue){
System.out.print(temp);
}
System.out.println();
System.out.println("通过反射设置字符串里面的值为\"Nice!!\"");
char replaceValueChar[]={'N','i','c','e','!','!'};
i.set(a1.tempString, replaceValueChar);
System.out.println("设置成功!!!");
System.out.println("再次通过反射获取a1.tempString里面的值");
tempStringCharArrayValue=(char[]) i.get(a1.tempString);
for(char temp:tempStringCharArrayValue){
System.out.print(temp);
}
System.out.println("\n通过a1对象直接输出tempString的值!");
System.out.println(a1.tempString);
System.out.println("请问现在a1.tempString的地址是否与原来的字符串world相同??");
System.out.println(a1.tempString=="wide world");
System.out.println("见证奇迹的时候到了!!");
System.out.println("wide world".toString());
}
}
}
}
输出结果:
非常不错哈!!
"wide world".toString()竟然输出"Nice!!"??
哼哼。厉害哦!!
总结:
- 我们通过反射能够修改String里面的值。
要想知道为什么"wide world".toString()
输出"Nice!!"
,你就得知道几个概念。
- 字符串是被保存在常量池中的
- 字符串被保存在常量池中对应着一个唯一的字符串序列和一个String对象引用,而该对象被保存在堆内存中。所以这也是为什么直接给String变量赋值后(
String a="Hello";
)就能够像String对象那样使用。 - 其中的字符串序列和应用之间是一个键值表的形式。
- 我们给一个String变量赋值的过程是这样的,假如我们这里
String a="Hello";
,首先JVM会在方法区的常量池里面找是否有"Hello"
这个字符串对应的字符串序列,如果有就返回对应的引用,如果没有就自己添加该字符串的序列和创建对象引用。
所以为什么"wide world".toString()
会输出"Nice!!"
??
当JVM在方法区的常量池中找"wide world"
这个字符串序列时,JVM能够很轻松的找到,并且返回该字符串序列对应的引用。但是呢??该String对象的值已经被我们通过反射修改了,变成了"Nice!!"
所以也就看到了我们上面的现象!!
注意,该字符串序列对应的引用对象真实内存地址是没有发生改变的。我们改变的只是改应用的值!!并且我们的伪内存地址(类的全路径名称@hashcode)也没有发生改变。
如果还是不能够明白结果为什么是这样,可以参考这篇博客:String字符串被创建的原理过程
如果看本博客有其它不明白的,比如第一句,可以查看博客:Java内存图以及堆、栈、常量区、静态区、方法区的区别
上面的例子,笔者是直接替换String的char[] 数组。下面我们不替换char数组,而是直接修改char数组里面的值,起到的效果还是一样的!!
准备基础类:
public class A {
public final String tempString="world";
public String getTempString() {
return tempString;
}
}
测试类:
public class FinalString02 {
public static void main(String[] args) throws IllegalArgumentException, IllegalAccessException {
A a1=new A();
A a2=new A();
System.out.println("判断a1.tempString与a2.tempString伪内存地址是否相同!!");
System.out.println(a1.tempString==a2.tempString);
System.out.println("输出a1.tempString的值");
System.out.println(a1.tempString);
System.out.println("对比a1.tempString与\"world\"是否相同");
System.out.println(a1.tempString=="world");
System.out.println("现在通过反射开始尝试修改a1.tempString的内容");
Field tempPropertyFields[] = a1.tempString.getClass().getDeclaredFields();
for(Field tempField:tempPropertyFields){
if(tempField.getName().equals("value")){
tempField.setAccessible(true);
char stringChars[]=(char[]) tempField.get(a1.tempString);
System.out.println("开始修改字符串的值!!");
stringChars[0]='N';
stringChars[1]='i';
stringChars[2]='c';
stringChars[3]='e';
stringChars[4]='!';//发现没有,我这里的长度和a1.tempString的原始长度是相同的。因为真正的内存区里面的大小只有这么点大!!切记不能够超出,否则会报数组越界!!!
System.out.println("修改成功!!");
System.out.println("尝试输出修改后的a1.tempString的值!!");
System.out.println(a1.tempString);
System.out.println("尝试对比a1.tempString和\"world\"的值是否相同!");
System.out.println(a1.tempString=="world");
System.out.println("让我们见证奇迹吧!!");
System.out.println("world".toString());
}
}
}
}
输出结果:
判断a1.tempString与a2.tempString伪内存地址是否相同!!
true
输出a1.tempString的值
world
对比a1.tempString与"world"是否相同
true
现在通过反射开始尝试修改a1.tempString的内容
开始修改字符串的值!!
修改成功!!
尝试输出修改后的a1.tempString的值!!
Nice!
尝试对比a1.tempString和"world"的值是否相同!
true
让我们见证奇迹吧!!
Nice!
第一个例子的特点就是能够重新赋值任意长度的char数组,而第二个只能够在当前数组大小中修改char数组里面的内容。
好了,写完了!!Nice!!!