如何理解String是不可变的

如何理解String是不可变的

1.何为不可变

众所周知, 在Java中, String类是不可变的。那么到底什么是不可变的对象呢?

可以这样认为:

如果一个对象,在它创建完成之后,不能再改变它的状态,那么这个对象就是不可变的。不能改变状态的意思是,不能改变对象内的成员变量,包括基本数据类型的值不能改变,引用类型的变量不能指向其他的对象,引用类型指向的对象的状态也不能改变。

《java concurrency in practice》一书给出了一个粗略的定义:对象一旦创建后,其状态不可修改,则该对象为不可变对象。 一般一个对象满足以下三点,则可以称为是不可变对象:

  1. 其状态不能在创建后再修改;
  2. 所有域都是final类型;
  3. 其构造函数构造对象期间,this引用没有泄露。

这里重点说明一下第2点,一个对象其所有域都是final类型,该对象也可能是可变对象。因为final关键字只是限制对象的域的引用不可变,但无法限制通过该引用去修改其对应域的内部状态。

2.对象和对象引用

在此之前,我们先看一看下面这行代码。

打印结果:

首先创建一个String对象s,然后让s的值为ABC, 然后又让s的值加等为ABCabc。 从打印结果可以看出,s的值确实改变了。那么怎么还说String对象是不可变的呢? 其实这里存在一个误区: s只是一个String对象的引用,并不是对象本身。对象在内存中是一块内存区,成员变量越多,这块内存区占的空间越大。引用只是一个4字节的数据,里面存放了它所指向的对象的地址,通过这个地址可以访问对象。

也就是说,s只是一个引用,它指向了一个具体的对象,当s=ABC; 这句代码执行过之后,又创建了一个新的对象ABCabc, 而引用s重新指向了这个新的对象,原来的对象ABC还在内存中存在,并没有改变。内存结构如下图所示:

如果希望存放的String可以调整大小,而不是创建新的内存来存放新的对象,可以使用StringBuffer这个类来存放。这个内存可以调整大小。而不会抛弃。

3.String为什么不可变

我们打开String的源码查看

String类是用final关键字修饰,这说明String不可继承。再看下面,String类的主力成员字段value是个char[ ]数组,而且是用final修饰的。final修饰的字段创建以后就不可改变。

value用final修饰,编译器不允许我把value指向堆区另一个地址。

但如果我直接对数组元素动手,那不就没什么问题了嘛。

4.不可变有什么好处

最简单的原因就是安全,我们下面再来看一个例子。

typescript 复制代码
package com.Leo.exer.string;
​
/**
 * @author : Leo
 * @version 1.0
 * @date 2023-11-21 9:38
 * @description : String 测试用例
 */
public class Test {
        //不可变的String
        public static String appendStr(String s){
            s+="bbb";
            return s;
        }
​
        //可变的StringBuilder
        public static StringBuilder appendSb(StringBuilder sb){
            return sb.append("bbb");
        }
​
        public static void main(String[] args){
            //String做参数
            String s = new String("aaa");
            String ns=Test.appendStr(s);
            System.out.println("String s >>> "+s);
​
            //StringBuilder做参数
            StringBuilder sb=new StringBuilder("aaa");
            StringBuilder sbn=Test.appendSb(sb);
            System.out.println("StringBuilder sbn >>> "+sb);
        }
}

运行结果:

如果我们不小心像上面例子里,直接在传进来的参数上加"bbb",因为Java对象参数传的是引用,所以可变的的StringBuffer参数就被改变了。可以看到变量sb在Test.appendSb(sb)操作之后,就变成了"aaabbb"。有的时候这可能不是程序员的本意。所以String不可变的安全性就体现在这里。

  1. 只有当字符串是不可变的,字符串池才有可能实现。字符串池的实现可以在运行时节约很多heap空间,因为不同的字符串变量都指向池中的同一个字符串。但如果字符串是可变的,那么String interning将不能实现(String interning是指对不同的字符串仅仅只保存一个,即不会保存多个相同的字符串),因为这样的话,如果变量改变了它的值,那么其它指向这个值的变量的值也会一起改变。
  2. 如果字符串是可变的,那么会引起很严重的安全问题。譬如,数据库的用户名、密码都是以字符串的形式传入来获得数据库的连接,或者在socket编程中,主机名和端口都是以字符串的形式传入。因为字符串是不可变的,所以它的值是不可改变的,否则黑客们可以钻到空子,改变字符串指向的对象的值,造成安全漏洞。
  3. 因为字符串是不可变的,所以是多线程安全的,同一个字符串实例可以被多个线程共享。这样便不用因为线程安全问题而使用同步。字符串自己便是线程安全的。
  4. 类加载器要用到字符串,不可变性提供了安全性,以便正确的类被加载。譬如你想加载java.sql.Connection类,而这个值被改成了myhacked.Connection,那么会对你的数据库造成不可知的破坏。

5.String小结

Java中的String类被设计成不可变(immutable)的,这里所说的"不可变"是指一旦一个String对象被创建,那么它所包含的字符序列就不能被改变。

  1. 存储方式

    • 在Java中,String对象内部使用字符数组保存字符串数据。这个字符数组是被标记为final的,这就意味着数组的引用不能被更改。一旦分配了数组空间和内容,就无法再更改数组中存储的字符。
  2. 创建String对象

    • 当你创建一个String对象时,例如通过String s = "hello";,实际上就在字符串池中创建了一个String实例(如果池中不存在的话)。
    • 如果你试图"更改"String对象如s = s + " world";,这实际上并不是更改原有的String对象,而是在字符串池中创建了一个新的String对象,并把它的引用指向变量s。原来的字符串"hello"仍然存在,只是我们没有任何变量引用它了。
  3. 效率和安全性

    • 不可变的好处之一在于效率。不可变对象因为不可更改,所以它们可以自由地共享。例如,多个引用可以安全地指向同一个字符串,而不需要担心其中的一个引用会无意中修改字符串内容,影响到其他的引用。
    • 另一个好处是安全性。由于String的不可变性,它的值在创建之后就不能被更改,这意味着它是线程安全的,可以在多线程环境下被共享而不需要额外的同步措施。
  4. String Pool特性

    • Java中有一个特别的字符串池,用于存储字符串字面量。这是可能的因为字符串是不可变的。如果字符串是可变的,那么一个字符串的改变可能影响到引用同一个字符串的其他地方,这会造成显著的错误和安全问题。
  5. hashCode的缓存

    • 因为String是不可变的,其hashCode的值也是不会改变的,这就允许String类缓存hashCode值。因为String在Java中经常作为HashMap和HashSet的键,所以不可变性带来的hashCode缓存能显著提高这些集合操作的性能。
  6. 设计决策和实现

    • String的不可变性是一项设计决策,它有助于简化代码和提高性能。在一些需要可变字符串的情况下,Java提供了StringBuffer和StringBuilder类,它们包含了可以更改字符串内容的API。

6.总结

以上便是本文的全部内容,本人才疏学浅,文章有什么错误的地方,欢迎大佬们批评指正!我是Leo,一个在互联网行业的小白,立志成为更好的自己。

如果你想了解更多关于Leo,可以关注公众号-程序员Leo,后面文章会首先同步至公众号。

相关推荐
沈韶珺44 分钟前
Visual Basic语言的云计算
开发语言·后端·golang
沈韶珺1 小时前
Perl语言的函数实现
开发语言·后端·golang
美味小鱼1 小时前
Rust 所有权特性详解
开发语言·后端·rust
我的K84091 小时前
Spring Boot基本项目结构
java·spring boot·后端
慕璃嫣2 小时前
Haskell语言的多线程编程
开发语言·后端·golang
晴空๓2 小时前
Spring Boot项目如何使用MyBatis实现分页查询
spring boot·后端·mybatis
Hello.Reader6 小时前
深入浅出 Rust 的强大 match 表达式
开发语言·后端·rust
customer0810 小时前
【开源免费】基于SpringBoot+Vue.JS体育馆管理系统(JAVA毕业设计)
java·vue.js·spring boot·后端·开源
计算机-秋大田13 小时前
基于微信小程序的电子竞技信息交流平台设计与实现(LW+源码+讲解)
spring boot·后端·微信小程序·小程序·课程设计