如何理解String是不可变的

news2024/11/20 11:30:42

在这里插入图片描述

✅作者简介:大家好,我是Leo,热爱Java后端开发者,一个想要与大家共同进步的男人😉😉
🍎个人主页:Leo的博客
💞当前专栏:每天一个知识点
✨特色专栏: MySQL学习
🥭本文内容:如何理解String是不可变的

🖥️个人小站 :个人博客,欢迎大家访问
📚个人知识库: 知识库,欢迎大家访问

✨✨ 粉丝福利订阅✨✨

在这里插入图片描述

Leo哥收集了一些关于面试以及其他学习资源,这里分享给大家,各位卷王快收下吧!!!

1.何为不可变

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

可以这样认为:

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

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

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

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

2.对象和对象引用

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

image-20231121095619930

打印结果:

image-20231121095705865

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

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

image-20231121100231507

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

3.String为什么不可变

我们打开String的源码查看

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

image-20231121100550849

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

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

image-20231121100622372

4.不可变有什么好处

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

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);
        }
}

运行结果:

image-20231121100836941

如果我们不小心像上面例子里,直接在传进来的参数上加"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,后面文章会首先同步至公众号。

ToLeoJavaer公众号 (微信搜索程序员Leo)

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1232914.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

天猫超市电商营销系统:无代码开发实现API连接集成

无代码开发实现天猫超市与电商系统的高效连接 天猫超市,作为天猫推出的网络零售超市,为广大网购消费者提供了一站式的购物服务。而通过无代码开发的方式,天猫超市能够实现与各种电商系统的连接和集成,这种方式无需进行繁琐的API开…

最前端|低代码平台轻松设计可视化图表【内含网站资源】

在前端设计中,我们经常需要使用可视化图表来呈现数据和信息。然而,每次都要自己从头开始设计图表未免太过繁琐。为了解决这个问题,我们调研了low code平台上的可视化图表功能。 本篇文章为大家带来以下问题的解答: (1&…

Spark---介绍及安装

一、Spark介绍 1、什么是Spark Apache Spark 是专为大规模数据处理而设计的快速通用的计算引擎。Spark是UC Berkeley AMP lab (加州大学伯克利分校的AMP实验室)所开源的类Hadoop MapReduce的通用并行计算框架,Spark拥有Hadoop MapReduce所具有的优点;但…

python数据结构与算法-07_哈希表

哈希表 不知道你有没有好奇过为什么 Python 里的 dict 和 set 查找速度这么快呢,用了什么黑魔法吗? 经常听别人说哈希表(也叫做散列表),究竟什么是哈希表呢?这一章我们来介绍哈希表,后续章节我们会看到 Python 中的字…

申请注册苹果iOS企业级开发者证书需要公司拥有什么规模条件

在全球范围内,iOS应用市场的规模和影响力不断增长,企业级应用在其中扮演着重要角色。为了在苹果设备上开发和发布内部应用,企业需要获取苹果的iOS企业级开发者证书。文章旨在探讨企业在申请此证书时需要满足的条件、注册流程,以及…

QT 搭建opencv 环境

1. 准备工具CMake 一、CMake介绍 CMake是一个被广泛使用的、开源免费并且完全跨平台的构建工具,可以用简单的语句来描述所有平台的安装(编译过程)。它能够输出各种各样的makefile或者project文件,能测试编译器所支持的C特性,类似UNIX下的aut…

代码随想录 11.20 || 单调栈 LeetCode 503.下一个更大元素Ⅱ、42.接雨水

503.下一个更大元素Ⅱ 本题与 496.下一个更大元素Ⅰ 如出一辙,求在给定一维数组中,每个元素右起第一个更大元素。不同之点在于,本题要求将一维数组视为首尾相连的,最后一个元素的下一个元素是第一个元素。问题的重点在于 如何使用…

正版软件|PhotoPad 照片/图像编辑工具

编辑照片/图像从未如此简单! 关于PhotoPad PhotoPad是本世纪最稳定、最易用且功能最全面的照片编辑器之一。 轻松编辑数码照片和其他图片。支持所有流行的图像格式快速裁剪、旋转、调整大小和翻转照片。 PhotoPad旨在快速打开和编辑照片,轻松无忧&…

Echarts设置环形图中心文字

方式一:title 通过在最外层设置title 配置项 title 同时也是环形图标题的配置项,只是通过left和top强制让其移动到环形图中间位置;提前设置好环形图 title: {// 图形标题(如果想要换行则使用ES6 模板字符串)// 例如: 示例// 这…

ros2工作空间

我们先不管ros2工作空间是什么样子的,如果是我自己来搞一个工作空间,我一定是这样安排 一个文件夹用来放自己存放的文件,。。。。。。。。。。对应src文件夹 一个文件夹用来放编译后的文件,。。。。。。。。。。。对应intall文件…

U4_1:图论之DFS/BFS/TS/Scc

文章目录 一、图的基本概念二、广度优先搜索(BFS)记录伪代码时间复杂度流程应用 三、深度优先搜索(DFS)记录伪代码时间复杂度流程时间戳结构BFS和DFS比较 四、拓扑排序一些概念有向图作用拓扑排序 分析伪代码时间复杂度彩蛋 五、强…

OpenAI一朝领导者山姆·奥尔特曼被解除职务,领导能力不再被认可?

原创 | 文 BFT机器人 在11月17日,OpenAI Inc.宣布CEO山姆奥尔特曼 (Sam Altman) 离职。掌舵人发生重大转变,董事会却在这个时候将公司首席技术官 Mira Murati推举为临时首席执行官,这一操作顿时引起了热议。 这突如其来的变化标志着OpenAI的…

C++实战学习笔记

文章目录 erase()uniquevector的insert()std::string::npos erase() (1)erase(pos,n); 删除从pos开始的n个字符,比如erase(0,1)就是删除第一个字符 (2)erase(position);删除position处的一个字符(position是个string类…

element表格分页+数据过滤筛选

目录 前言效果展示分页效果展示搜索效果展示 代码分析分页功能过滤数据功能 全部代码 前言 在el-element的标签里的tableData数据过多时,会导致表格页面操作卡顿。为解决这一问题,有以下解决方法: 分页加载: 将大量数据进行分页&…

Android File Transfer(安卓文件传输工具)

Android File Transfer 是一款安卓文件传输工,它允许在Mac操作系统和Android设备之间进行文件传输。 该软件通过USB连接将文件从Mac电脑传输到连接的Android设备,或者反过来从Android设备传输文件到Mac电脑。这包括照片、视频、音乐、文档和其他文件类型…

最新企业服务总线ESB的国内主要厂商和开源厂商排名,方案书价格多少

企业服务总线ESB是什么? ESB平台(企业服务总线,Enterprise Service Bus)是一种企业级集成平台,它提供了一种开放的、基于标准的消息机制,通过简单的标准适配器和接口,来完成粗粒度应用&#xff…

Qt程序打包成.exe可执行文件

1.使用Release进行编译 2.找到编译成功的地址: 找到对应的目录 3.把SerialTool.exe文件单独复制到一个文件夹,这里我直接在桌面创建一个SerialTool文件夹,这时候直接运行是不行的,我们需要把库都导进去 4. 在安装目录找到如下这个文件,点击打开,找到你电脑对应的版本即可,我这…

数据仓库:架构之详解Kappa和Lambda

目录 一、前言 二、架构详解 1 Lambda 架构 1.1 Lambda 架构组成 1.2 Lambda 特点 1.3 Lambda 架构的优点 1.4 Lambda 架构的不足 2 Kappa 架构 2.1 Kappa 架构的核心组件 2.2 Kappa 架构优点 2.3 Kappa 架构的注意事项 三、区别对比 四、选择时考虑因素 一、前言 …

录屏软件自动开启录视频,是如何实现的?

工作要留痕,作为职场人的一项必备技能,因此许多人在做一些重要操作的时候,就会提前开启录屏软件,把操作的每一个步骤进行录制,以避免在出现问题的时候进行检查。当每天都需要在固定的时间点重复某项工作的时候&#xf…

【AI实用技巧】GPT写sql统计语句

编写sql的统计语句是一项复杂的任务,特别是涉及多表的情况下。但有了GPT的帮助,一切变得轻松愉快。 AI7号 - 最强人工智能(GPT)中文版https://ai7.pro/s/9v2um 举例说明 有表结构如下: users(user_id, name) bills(…