A股上市公司传智教育(股票代码 003032)旗下技术交流社区北京昌平校区

 找回密码
 加入黑马

QQ登录

只需一步,快速开始

© 奚华 中级黑马   /  2012-11-18 02:13  /  4174 人查看  /  18 人回复  /   1 人收藏 转载请遵从CC协议 禁止商业使用本文

本帖最后由 奚华 于 2012-11-18 18:42 编辑

  • 、JDK7 API文档上说:Strings are constant; their values cannot be changed after they are created. ”,翻译过来就是说:”字符串是常量;它们的值在创建之后不可改变。“我查阅了String类的API文档,发现String类底层用一个私有的常量字符数组来保存字符,如下所示:
             
    1. /** The value is used for character storage. */
    2.     private final char value[];
    复制代码
    既然这个数组是final的那么这个数组变量也就是value就不可能指向其他的char数组,但是在我实现的代码里,value被我改变了。


    、另外对于传入方法的参数,使用的是值传递,就是说即使传入的是一个引用类型而不是基本类型,也不能使这个参数改而引用其他对象,比如如下例子:
    1. class  Test
    2. {
    3.        public static void main(String[] args)
    4.       {
    5.             changeStr(str1);//无法改变str1引用的值
    6.             System.out.println(str1);//仍然会打印出A
    7.       }

    8.       public static void changeStr(String str)//试图改变字符串的内容,但是不会成功
    9.      {
    10.              str="C";
    11.      }

    12.       public static String str1="A";
    13.       public static String str2="B";
    14. }
    复制代码
    该例子中change方法不能改变传入的参数所指向的对象的值。



    三、基于以上两点我们试着来改一下String变量


    1. import java.lang.reflect.Field;


    2. public class StringChange {
    3.         
    4.       static  String str="ABC";

    5.      /**
    6.      * @param args
    7.      * @throws Exception
    8.      */
    9.     public static void main(String[] args) throws Exception {
    10.          // TODO Auto-generated method stub
    11.                
    12.         System.out.println("---打印改变前的str字符串的值---");
    13.         System.out.println(str);//打印出来ABC
    14.                
    15.          change(str);//改变String变量的值
    16.                
    17.          System.out.println("---打印改变后的str字符串的值---");
    18.          System.out.println(str);//打印出来CBA
    19.                
    20.         }
    21.         
    22.   /**
    23.   * 该方法运用反射原理改变了传入的参数的值,具体来说就是改变了字符串的值
    24.   * @param str
    25.   * @throws Exception
    26.   */
    27.     public static void change(String str)throws Exception
    28.    {
    29.           Class cls=str.getClass();//获得str的Class对象
    30.           Field fil=cls.getDeclaredField("value");//获得String类中的char数组value[]的Filed变量
    31.           fil.setAccessible(true);//由于value变量是一个private的所以设置权限
    32.                
    33.          char[] ch={'C','B','A'};
    34.          fil.set(str, ch);//改变value所指向的数组,使value指向ch数组
    35.    }

    36. }
    复制代码
    从以上例子我们可以看出反射机制的强大,以上思路来源于黑马训练营张孝祥老师的视频。对该段代码我仍然存有疑问,为什么一个private final char[] value 会被修改掉?是不是我理解错了?期待解答!!!


评分

参与人数 1技术分 +1 收起 理由
古银平 + 1 赞一个!

查看全部评分

18 个回复

倒序浏览
public static void changeStr(String str)//试图改变字符串的内容,但是不会成功
     {
             str="C";
     }

楼主的这一块,的确是无法改变字符串的内容,这个方法进行的是值传递,不是引用传递,根本修改的就不是str字符串本体。
对于楼主的反射,我很佩服,真是用对了地方,相信这个方法一定会有他的用武之地。
回复 使用道具 举报
谢谢,共同努力
回复 使用道具 举报
看了看这个,确实是可以修改的,
set(obj,value)方法的文档中确实有说明:
对于设置setAccessible(true)及non-static的情况下可以对final域(field)进行修改,
这种修改在进行反序列化和实例的final域未初始化,其他的程序尚不能访问之前进行才是有意义的操作,
否则其他的程序要访问的可能还是这个域(field)的原始值,这时就可能会出现错误。

还有就是,这里改变的目标值ch的长度要不小于原始值str的长度,否则会报错,内部应该是以原始值str的长度为基准做循环的,
值虽可变,长度始终是不可变的。

评分

参与人数 1技术分 +1 收起 理由
古银平 + 1 赞一个!

查看全部评分

回复 使用道具 举报
应用反射就相当于暴力破解,一般是“犯法的”,类定义私有变量,本身是不让外界直接访问的,这是规则,你用反射破坏了这个规则,就好像final定义是不能修改的,你却能用反射修改,这些定义只是规定了正常途径的吧~
回复 使用道具 举报
本帖最后由 奚华 于 2012-11-18 09:37 编辑
李连闯 发表于 2012-11-18 09:13
看了看这个,确实是可以修改的,
set(obj,value)方法的文档中确实有说明:
对于设置setAccessible(true)及n ...
  1. import java.lang.reflect.Field;


  2. public class StringChange {
  3.         
  4.         static  String str="ABC";

  5.         /**
  6.          * @param args
  7.          * @throws Exception
  8.          */
  9.         public static void main(String[] args) throws Exception {
  10.                 // TODO Auto-generated method stub
  11.                
  12.                 System.out.println("---打印改变前的str字符串的值---");
  13.                 System.out.println(str);//打印出来ABC
  14.                
  15.                 change(str);//改变String变量的值
  16.                
  17.                 System.out.println("---打印改变后的str字符串的值---");
  18.                 System.out.println(str);//打印出来DSWQ,长度改变了
  19.                
  20.         }
  21.         
  22.         /**
  23.          * 该方法运用反射原理改变了传入的参数的值,具体来说就是改变了字符串的值
  24.          * @param str
  25.          * @throws Exception
  26.          */
  27.         public static void change(String str)throws Exception
  28.         {
  29.                 Class cls=str.getClass();//获得str的Class对象
  30.                 Field fil=cls.getDeclaredField("value");//获得String类中的char数组value[]的Filed变量
  31.                 fil.setAccessible(true);//由于value变量是一个private的所以设置权限
  32.                
  33.                 char[] ch={'D', 'S','W','Q'};
  34.                 fil.set(str, ch);//改变value所指向的数组,使value指向ch数组
  35.         }

  36. }
复制代码
回复 使用道具 举报
奚华 中级黑马 2012-11-18 09:53:44
7#
为什么同样是final类型,String类里的private final char[] value 会被改变,而下面程序的final String str却没有被改变掉??
  1. import java.lang.reflect.Field;


  2. public class ReflectTest {
  3.         private final  String str="ABC";

  4.         /**
  5.          * @param args
  6.          * @throws Exception
  7.          */
  8.         public static void main(String[] args) throws Exception {
  9.                 // TODO Auto-generated method stub
  10.                 ReflectTest rt=new ReflectTest();
  11.                
  12.                 System.out.println("---打印改变前的str字符串的值---");
  13.                 System.out.println(rt.str);//打印出来ABC
  14.                
  15.                 rt.change(rt);
  16.                
  17.                 System.out.println("---打印改变后的str字符串的值---");
  18.                 [color=DarkRed]System.out.println(rt.str);//打印出来还是ABC,没有被改变[/color]
  19.         }
  20.        
  21.        
  22.         /**
  23.          * 试图改变obj对象里的的字符串成员
  24.          * @param obj
  25.          * @throws Exception
  26.          */
  27.         public void change(Object obj) throws Exception
  28.         {
  29.                 Field[] fild=obj.getClass().getDeclaredFields();
  30.                 for(Field f:fild)
  31.                 {
  32.                         if(!f.isAccessible())//设置权限
  33.                                 f.setAccessible(true);
  34.                         if(f.getType()==String.class)
  35.                         {
  36.                                 String str=new String("CBA");//想把这些字符串设置为"CBA"
  37.                                 f.set(obj, str);
  38.                         }
  39.                 }
  40.         }

  41. }
复制代码
回复 使用道具 举报
看来加了final就无法改变了。佩服楼主的专研精神。
回复 使用道具 举报
本帖最后由 李连闯 于 2012-11-18 20:42 编辑
奚华 发表于 2012-11-18 09:31

修改后的结果是DSWQ么?我的myeclipse运行结果是DSW的,

捕获.JPG (77.84 KB, 下载次数: 52)

捕获.JPG
回复 使用道具 举报
奚华 中级黑马 2012-11-18 20:42:17
10#
本帖最后由 奚华 于 2012-11-18 20:50 编辑

我的Eclipse运行是DSWQ,jre是javase1.7

捕获.PNG (24.31 KB, 下载次数: 50)

捕获.PNG
回复 使用道具 举报
奚华 发表于 2012-11-18 20:42
我的Eclipse运行是DSWQ,jre是javase1.7

装了下1.7的试了下,
关于长度不同赋值报错这个问题在1.6和1.7两个环境里面运行是不一样的,在1.7里面,对这个是改进了的,
回复 使用道具 举报
奚华 发表于 2012-11-18 09:53
为什么同样是final类型,String类里的private final char[] value 会被改变,而下面程序的final String str ...


这里的话其实已经把str的值修改了,
Field f = rt.getClass().getDeclaredField("str");
System.out.println("f.get(rt)--"+f.get(rt));//通过反射来获取值是"CBA"的,证实已经是被修改了的
至于为什么显示的还是之前的"ABC"的原因是声明final变量str的时候就给赋了值,
private final String str = "ABC";
编译的时候这里初始化指定的"ABC"会优先于反射修改的"CBA"被调用,
所以如果简单的rt.str的调用,显示的会是"ABC",而不是修改后的"CBA";
放到构造方法里初始化,就OK的,
public ReflectTest(){
        str = "ABC";
}
回复 使用道具 举报
楼主研究的够深,佩服,学习,赞!
回复 使用道具 举报
奚华 中级黑马 2012-11-18 23:41:38
14#
李连闯 发表于 2012-11-18 23:11
这里的话其实已经把str的值修改了,
Field f = rt.getClass().getDeclaredField("str");
System.out.pri ...

你的意思就是说,编译器坚定的认为只要是声明后就赋值的final成员一定是不可变的,所以编译器在编译最后的System.out.println(str)这句代码时只是简单的从常量池里拿出编译时就定好的str的值而没有考虑到这个值会发生改变。
那么另外一种情况下把final str放在构造方法里,这样的话,编译器无法事先确定str的值,就编译出另外的指令,在每一次使用到str时都去看一次str的值!
回复 使用道具 举报
奚华 中级黑马 2012-11-18 23:42:10
15#
我看了用 javap 反编译出的代码,的确是你所说的!
回复 使用道具 举报
本帖最后由 汤瑞贺 于 2012-11-19 01:53 编辑

不知不觉看此贴一个多小时过去了,又写了一个多小时(下面是自己的小理解,可能不准确,欢迎提意见{:soso_e100:})
感觉你的观点“字符串不可变”是理解就是有点不对的吧!
楼主所写得用反射代码所实现的效果和下面面这几行代码效果雷同吧,这样不就“证明了-->把字符串该变了吗”,用反射岂不多此一举?当然我认为你没改变。
  1. class StringChange
  2. {public static String str="ABC";
  3. public static void main(String[] args)
  4. {
  5. System.out.println("---打印改变前的str字符串的值---");
  6. System.out.println(str);//打印出来ABC
  7. str="CBA";
  8. System.out.println("---打印改变后的str字符串的值---");
  9. System.out.println(str);//打印出来CBA
  10. }
  11. }

复制代码

  1. /*
  2. 下面测试一下你的代码
  3. 只在change(String str)函数里添加一句代码,就改不掉了...

  4. */
  5. import java.lang.reflect.Field;

  6. public class StringChange {
  7.         
  8.       static  String str="ABC";</P>
  9.      /**
  10.      * @param args
  11.      * @throws Exception
  12.      */
  13.     public static void main(String[] args) throws Exception {
  14.          // TODO Auto-generated method stub
  15.                
  16.         System.out.println("---打印改变前的str字符串的值---");
  17.         System.out.println(str);//打印出来ABC
  18.                
  19.          change(str);//改变String变量的值
  20.                
  21.          System.out.println("---打印改变后的str字符串的值---");
  22.          System.out.println(str);//打印出来CBA
  23.                
  24.         }
  25.         
  26.   /**
  27.   * 该方法运用反射原理改变了传入的参数的值,具体来说就是改变了字符串的值
  28.   * @param str
  29.   * @throws Exception
  30.   */
  31.     public static void change(String str)throws Exception
  32.    {    str="任意一个不同于初始化的字符串";//这样就改不了
  33.           Class cls=str.getClass();//获得str的Class对象
  34.           Field fil=cls.getDeclaredField("value");//获得String类中的char数组value[]的Filed变量
  35.           fil.setAccessible(true);//由于value变量是一个private的所以设置权限
  36.                
  37.          char[] ch={'C','B','A'};
  38.          fil.set(str, ch);//改变value所指向的数组,使value指向ch数组
  39.    }
  40. }
复制代码
至此,图示你代码所实现的功能:

你只是改变了str指向(这就像C语言中的指针)str不是字符串,str指向的变量才是字符串,即上图矩形框,“矩形框”才是字符串变量(实则内存地址吧,没查资料自己理解),“矩形框里的内容”才是变量的值,这个是改变不了的..........

ps:  String str1="ABC";
       String str2="ABC";
      


回复 使用道具 举报
奚华 发表于 2012-11-18 23:41
你的意思就是说,编译器坚定的认为只要是声明后就赋值的final成员一定是不可变的,所以编译器在编译最后 ...

说的好啊,哈哈,
回复 使用道具 举报
奚华 中级黑马 2012-11-19 09:45:43
18#
本帖最后由 奚华 于 2012-11-19 09:47 编辑
汤瑞贺 发表于 2012-11-19 01:41
不知不觉看此贴一个多小时过去了,又写了一个多小时(下面是自己的小理解,可能不准确,欢迎提意见{:soso_e ...

我要改的是成员变量str,你在change方法里面
  1. str="任意一个不同于初始化的字符串";//这样就改不了
复制代码
这样使str指向了一个方法内的对象,这样change方法其实在改方法内的str对象,当方法运行结束时,方法的局部变量str被舍弃了,所以看不到结果!看下面的代码:
  1. /*
  2. 下面测试一下你的代码
  3. 只在change(String str)函数里添加一句代码,就改不掉了...

  4. */
  5. import java.lang.reflect.Field;

  6. public class StringChange {
  7.         
  8.       static  String str="ABC";
  9.      /**
  10.      * @param args
  11.      * @throws Exception
  12.      */
  13.     public static void main(String[] args) throws Exception {
  14.          // TODO Auto-generated method stub
  15.                
  16.         System.out.println("---打印改变前的str字符串的值---");
  17.         System.out.println(str);//打印出来ABC
  18.                
  19.          change(str);//改变String变量的值
  20.                
  21.          System.out.println("---打印改变后的str字符串的值---");
  22.          System.out.println(str);//打印出来CBA
  23.                
  24.         }
  25.         
  26.   /**
  27.   * 该方法运用反射原理改变了传入的参数的值,具体来说就是改变了字符串的值
  28.   * @param str
  29.   * @throws Exception
  30.   */
  31.     public static void change(String str)throws Exception
  32.    {    str="任意一个不同于初始化的字符串";//这样就改不了
  33.           Class cls=str.getClass();//获得str的Class对象
  34.           Field fil=cls.getDeclaredField("value");//获得String类中的char数组value[]的Filed变量
  35.           fil.setAccessible(true);//由于value变量是一个private的所以设置权限
  36.                
  37.          char[] ch={'C','B','A'};
  38.          fil.set(str, ch);//改变value所指向的数组,使value指向ch数组
  39.          
  40.          //你在这里加一行看看改了没有,会打印出来CBA
  41.          System.out.println(str);
  42.    }
  43. }
复制代码
回复 使用道具 举报
奚华 发表于 2012-11-19 09:45
我要改的是成员变量str,你在change方法里面这样使str指向了一个方法内的对象,这样change方法其实在改方 ...

;P貌似是我搞错了,你确实改变变了成员变量
回复 使用道具 举报
您需要登录后才可以回帖 登录 | 加入黑马