String类型的值能够被反射改变和常量池的理解

介绍

文章写得有点杂,有对常量池的理解和字符串的值通过反射修改!!

开始讲解

先准备一个基础类:

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!!!

  • 2
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值