java中String真的不可变吗?其实这样可以真的改变String字符串

本文详细探讨了Java中String类型的不可变性,解释了其内部的char数组为何不能直接修改,并通过反射操作展示了如何在极端情况下改变String的内容。同时,文章也澄清了hashCode()方法在判断字符串是否变化时的局限性,提供了通过比较引用变化来验证字符串是否原地修改的方法。
摘要由CSDN通过智能技术生成

为什么说String类型不可变

  • String的源码里其实使用一个char数组来存储字符串的,String之所以说不可变,就是因为这个char数组它是private类型,而且String没有对外暴露和提供修改这个char数组的方法,因此我们无法更改这个char数组的值,所以String是不可变的

  • image-20220418084542067

  • 注意:这里char数组被final修饰不代表不可变,因为数组是引用类型,final修饰引用类型的时候代表引用不可更改,即我们只是不能将这个value属性指向新的引用,但是它原本指向的这个char数组中的字符我们是可以更改的

  • 因此,如果我们可以通过更改value指向的char数组本身,就可以改变字符串。正常情况下我们是无法更改的,这也是我们说字符串不可变的原因,但是我们通过极端方法也是可以更改的,例如使用反射可以得到这个私有的value属性,然后通过爆破我们就可以访问这个私有属性了,例如:

    String  s = "aabbc";
    Class<? extends String> aClass = s.getClass();
    Field value = aClass.getDeclaredField("value");//得到这个value属性
    value.setAccessible(true);//将它设置成可以访问
    char[] chars =  (char[]) value.get(s);
    System.out.println(s);//在修改之前输出s
    chars[0] = 'k';			//修改s
    System.out.println(s);//输出修改之后的s
    

    结果如下image-20220418091040434

    • 可以看出这个字符串确实被改变了

证明我们是真的修改了字符串

字符串的hashCode()方法

  • 这里我们不能用hashCode()返回的哈希值来判断是否是同一个字符串,因为hashCode码值不等于地址,字符串的hashCode码是通过对每个字符进行一定的规则的累加得到的,然而String类中有一个int类型的属性叫hash当这个hash=0的时候就会去计算这个String的hashCode码值,但是计算完后就会将计算结果赋值给这个hash属性;如果hash不等于0就返回这个属性值作为当前字符串的hashCode。因此我们第一次计算调用hashCode()的时候由于int类型的属性默认为0,所以会逐个字符计算hash值,第二次调用hashCode()的时候返回的hash值其实是第一次计算出来缓存好的,因此肯定是相同的,所以此时调用hashCode()方法返回的值无法用来判断是否为同一个字符串。

    • 源码如下

      private int hash; // Default to 0
      public int hashCode() {
          int h = hash;//得到当前属性hash的值
          if (h == 0 && value.length > 0) {//如果它为0就计算当前字符串的hashCode
              char val[] = value;
      
              for (int i = 0; i < value.length; i++) {
                  h = 31 * h + val[i];
              }
              hash = h;//计算完了后赋值给hash属性
          }
          return h;
      }
      
  • 我们可以这样判断修改过后的字符串到底是原地修改得到的还是重新赋值得到的:将这个字符串的引用赋值给另一个字符串变量tempC,我们修改这个字符串前后都输出这个tempC,如果我们是原地修改,那么这个tempC输出的值前后两次肯定不同;如果是重新赋值,那么tempC的值前后两次肯定一样,因为我们如果是重新赋值的话只是更改了s的引用,并没有更改tempC的引用

    • String  s = "aabbc";
      Class<? extends String> aClass = s.getClass();
      Field value = aClass.getDeclaredField("value");//得到这个value属性
      value.setAccessible(true);//将它设置成可以访问
      char[] chars =  (char[]) value.get(s);
      
      String tempC = s;
      System.out.println(tempC );//在修改之前输出tempC 
      chars[0] = 'k';			//修改s
      System.out.println(tempC );//输出修改之后的tempC 
      
      • 结果如下image-20220418092752119
    • 如果是重新赋值的情况,例如:

      String  s = "aabbc";
      String tempC = s;
      System.out.println(tempC);
      s = "sss";
      System.out.println(tempC);
      
      • 结果如下image-20220418092930118
  • 因此可知字符串确实是可以通过反射修改的

  • 9
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

一酒。

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值