对String为什么是不可变类的思考

本文探讨了Java中String类的不可变性,分析了其背后的原理,包括final关键字的使用和字符串池的概念。通过示例展示了即使通过反射也无法真正改变String对象的内部状态。不可变性带来的好处包括线程安全、提高效率以及在字符串常量池中的优化。此外,不可变性对于Java中的字符串操作和内存管理至关重要。
摘要由CSDN通过智能技术生成

String对象的创建

原理一:当使用任何方式来创建一个字符串对象str时,运行中的JVM虚拟机会拿着这个对象在String Pool(字符串常量池/缓冲区)中找是否存在内容相同的字符串对象,如果不存在,则在池中创建一个字符串str,否则不在池中添加。

原理二:Java中,只要使用new关键字来创建对象,则一定会在堆区或者栈区创建一个新的对象

String str1="helloworld";
String str2=new String("helloworld");
//str1指向的对象放在栈中,
//str2指向的对象放在堆中(存放的是字符串"helloworld"在内存中的地址)

原理三:使用指定或者使用纯字符串串联来创建String对象,则仅仅会检查维护String Pool中的字符串,池中没有就在池中创建一个,有就不用。但绝对不会在堆栈区中再去创建String对象

原理四:使用包含变量的表达式来创建String对象,则不仅仅会见检查维护String Pool,而且还会在堆栈区创建一个String对象

 

在此,有两问:

为什么String类是不可变类?

这样设计String类的好处是什么?

第一个问题,为什么String类不可变?

        我先去翻阅了String类的源码,正所谓万物皆可看源码。

        从上图可见,String类是被final这个关键字所修饰的,实现了Serializable、Comparable、CharSequence接口

        final关键字可以用来修饰引用、方法和类。在这里我不详细介绍final修饰引用和方法的作用,重点在final修饰类后的作用是什么。

  • 当用final修饰类时,该类成为最终类,无法被继承,该类就不能被其他类所继承;简称为“断子绝孙类”。当我们需要让一个类永远不被继承,此时就可以用final修饰,但要注意:final类中所有的成员方法都会隐式的定义为final方法

        从上述可知,String类是不能被继承,并且它的成员方法都默认为final方法

        String类的本质是一个不可变char数组String类的值实际上是通过char数组存储的,并且char数组privatefinal所修饰,因此字符串一旦创建就不能再修改了,从而保证了引用的不可变和对外的不可见

        扩展:我今天刚学成反射这个内容,既然私有的成员变量可以利用暴力反射成功修改属性,那么String类是否可以通过setAccessible()方法成功被修改呢?

/**
 * 字符串的测试
 *
 * @author HHLJ
 * @date 2022/02/28
 */
public class StringTest {
    public static void main(String[] args) throws NoSuchFieldException, IllegalAccessException {
        //创建三个字符串,一个用于修改,两个用于比较
        String str1 = "阿弥陀佛";
        String str2 = "阿弥陀佛";
        String str3 = "无量天尊";
        //阿弥陀佛
        System.out.println(str1);
        //获取String类中的value字段
        Field stringField = str1.getClass().getDeclaredField("value");
        //改变stringField的访问权限
        stringField.setAccessible(true);
        //修改str1的数据
        stringField.set(str1,new char[]{'无','量','天','尊'});
        //无量天尊
        System.out.println(str1);
        //1170471009
        System.out.println(str1.hashCode());
        //比较
        //true
        System.out.println(str1 == str2);
        //false
        System.out.println(str1 == str3);
    }
}

                扩展:由此可见,String类并不是完全不可变,我们利用反射绕过了私有权限修改了String底层的char[]数组。但是这种方式不推荐使用,不建议这么使用,这违反了 Java 对 String 类的不可变设计原则,会造成一些安全问题,整整活稍微了解了解就可以了。

第二个问题,这样设计String类的好处是什么?

        主要出于这两种原因:安全性、效率

安全性

        因为字符串是不可变的,所以是多线程安全的,同一个字符串实例可以被多个线程共享。这样便不用因为线程安全问题而使用同步。

                String被许多的Java类用来做参数,比如网络连接地址的url,文件路径path,反射机制所需要的String参数等通常都是用String类来保存,如果String对象不是固定不变的话,将会产生很多安全隐患。

效率

        字符串不变性保证了HashCode的唯一性,因此可以放心的进行缓存,这也是一种性能优化手段,意味着不必每次都取计算新的哈希值。

        只有当字符串是不可变的,字符串池才有可能实现,字符串常量池是java堆内存中一个特殊的存储区域,当创建一个String对象,假如此字符串值已经存在于常量池中,则不会创建一个新的对象,而是引用已经存在的对象。

        这样在大量使用字符串的情况下,可以节省内存空间提高效率,String的不可变性是实现该特性的最基本的一个必要条件。假设内存里的字符串内容可以被我们改来改去,那么这么做就毫无意义可言。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值