为什么Java String类的对象不可变

为什么Java String类的对象不可变

前言

学习 Java 的朋友对 Java 的 字符串都不陌生,字符串广泛应用在 Java 编程中,在 Java 中字符串属于对象,Java 提供了 String 类来创建和操作字符串。而且,Java 还规定了字符串是不可变的,也就是说,一旦一个字符串对象在 Java 中被创建,那么在死亡之前,它都至始至终保持着同一个状态。

需要理解

  1. 什么是不可变?
    • 不可变即状态不可变。
  2. String 对象是什么数据类型?
    • String 对象,也就是字符串,属于引用数据类型
  3. 引用数据类型和基本数据类型有什么区别?
    • 引用数据类型中存的是被引用的数据的内存地址,也就是索引数据;基本数据类型,其值是在内存中开辟出来的空间所存放在其中的值(可以认为空间名即为变量名)。
  4. final修饰基本类型变量时,不能对基本类型变量重新赋值,因此基本类型变量不能被改变。final 修饰引用型数据,只保证这个引用变量所引用的地址不会改变,即一直引用同一个对象,但这个对象本身可以发生改变。例如某个指向数组的 final 引用,它必须从此至终指向初始化时指向的数组,但是这个数组的内容完全可以改变。

ppPkwxx.png

String 对象的创建:使用new关键字时,将在堆中开辟一个新的空间,此时字符串对象中存放的就是这个空间的地址。直接初始化的 String 对象,则是在方法区的字符串池中。

内存图解

首先我们来看一下String类的两个主要成员变量,其中value指向的是一个字符串数组,字符串中的字符就是用这个value变量存储起来的,并且用final修饰,private也禁止了外界对它的访问。也就是说value一旦赋予初始值之后,value指向的地址就不能再改变了。虽然value指向的数组是可以改变的,但是String也没有提供相应的方法让我们去修改value指向的数组的元素。同时,private使得value的地址值不会被外界访问到,自然也就无从下手来修改该对象内存放的内容啦!

但是,新手可能会被产生的一些障眼法蒙蔽:

//创建字符串对象
        String s1 = "str";
        System.out.println(s1);
        //s1指向的内存地址
        System.out.println("s1的内存地址:"+System.identityHashCode(s1));
        String s2 = s1;
        s1 = "str2";
        System.out.println(s2);
        //s2的内存地址和s1的相同
        System.out.println("s2的内存地址:"+System.identityHashCode(s2));
        //s1的内存地址已经改变了
        System.out.println("s1的内存地址:"+System.identityHashCode(s1));
        System.out.println(s1);
        String s3 = new String();
        s3 = "str3";
        System.out.println("s3的内存地址:"+System.identityHashCode(s3));
        System.out.println(s3);

[ppPDv1H.md.png
在这里插入图片描述

s1被赋值"str2"后,原来字符常量池中的"str"状态并没有发生变化,而是重新创建了一个"str2"对象,此时,s1所指向的内存引用变量也不在是0X001,而是新的字符串对象的内存变量。

为什么 String 要设计得使其对象不可变

因为 String 对象缓存在 String 池中,由于缓存的字符串在多个操作者之间共享,因此始终存在风险,其中一个操作者的操作会影响所有其他操作者。

例如,如果一段代码将 “Hello” 的值更改为 “hello”,则所有其他操作者操作的String值将不会是自己想要的值,因此通过使 String 类不可变来避免这种风险。

同时,String 是 final 的,因此没有人可以通过扩展和覆盖行为来破坏 String 类的不变性、缓存、散列值的计算等。String 类不可变的另一个原因可能是由于 HashMap

对于键值来说,不可变性是非常的重要,保证用它们检索存储在 HashMap 中的值对象的准确性。由于 HashMap 的工作原理是散列,因此需要具有相同的key才能正常运行。如果在插入后修改了 String “key” 的内容,那么在插入和检索时就会生成两个不同的哈希码,造成 Map 中的值对象丢失。

String 真的真的真的 "不可变 " 吗?

虽然value是final修饰的,只是说明value不能再重新指向其他的引用。但是value指向的数组可以改变,所以我们如果真的想要改变value数组的内容党的话,可以通过反射得到String对象中的value属性,并且通过获得的value引用改变数组的结构。

public static void main(String[] args) throws Exception {
        String str = "Hello, World!";
        System.out.println("修改前的str:" + str);
        System.out.println("修改前的str内存地址:" + System.identityHashCode(str));
        // 获取String类中value字段
        Field valueField = String.class.getDeclaredField("value");

        // 禁用Java语言访问检查,以便我们可以修改value字段
        valueField.setAccessible(true);


        // 获取str对象中value字段的值
        char[] value = (char[]) valueField.get(str);

        // 更改字符串中的内容
        value[7] = 'J';
        value[8] = 'a';
        value[9] = 'v';
        value[10] = 'a';
        value[11] = '!';
        value[12] = '\0';
        System.out.println("修改后的str:" + str);
        System.out.println("修改前的str内存地址:" + System.identityHashCode(str));
    }

运行结果:

修改前的str:Hello, World!
修改前的str内存地址:460141958
修改后的str:Hello, Java! 
修改前的str内存地址:460141958
  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值