当前位置: 首页 > news >正文

Java中的String类真的不可变吗?

其实在Java中,String类被final修饰,主要是为了保证字符串的不可变性,进而保证了它的安全性。那么final到底是怎么保证字符串安全性的呢?接下来就让我们一起来看看吧。

一. final的作用

1.  final关键词修饰的类不可以被其他类继承,但是该类本身可以继承其他类,通俗地说就是这个类可以有父类,但不能有子类。

1

2

3

final class MyTestClass1 {

    // ...

}

2.  final关键词修饰的方法不可以被覆盖重写,但可以被继承使用。

1

2

3

4

5

class MyTestClass2 {

    final void myMethod() {

        // ...

    }

}

3.  final关键词修饰的基本数据类型被称为常量,只能被赋值一次。  

1

2

3

class MyTestClass3 {

    final int number = 100;

}

4.  final关键词修饰的引用数据类型变量,其值为地址值,该地址值不能改变,但该地址对应的数据对象可以被改变(其实这一点就和我们今天要说的内容有关了,在后面我会结合案例跟大家重点解释,大家一定要打起精神仔细学习哦)。

5.  final关键词修饰的成员变量,需要在创建对象前就赋值,否则会报错(即需要在定义时直接赋值)。

综上所述,我们可以知道,final在Java中是一个非常有用的关键字,主要可以提高我们代码的稳定性和可读性。当然,我们今天要讲解的重点是被final修饰的String类,所以接下来我们还是把目光转回到String身上来,看看String都有哪些特性吧!

二. 被final修饰的String类

为了让大家更好地理解String的不可变性,首先我要给各位简要地讲一下String的源码设计。从下面的这段源码中,我们可以搞清楚很多底层的设计思路,接下来就请大家跟着我一起来看看String的核心源码吧。

1

2

3

4

5

6

7

8

9

10

11

12

13

14

/**

 * ......其他略......

 *

 * Strings are constant; their values cannot be changed after they

 * are created. String buffers support mutable strings.

 * Because String objects are immutable they can be shared. For example:

 *

 * ......其他略......

 *

 */

public final class String

    implements java.io.Serializable, Comparable<String>, CharSequence {

     

    ......

我先把上面的源码及其注释,给大家作一个简单的解释:

● final:请参考第1小节对final特点的介绍;

● Serializable:用于序列化;

● Comparable<String>:默认的比较器;

● CharSequence: 提供对字符序列进行统一、只读的操作。

从这段源码及其注释中,我们可以得到下面这些结论:

● String类用final关键字修饰,说明String不可被继承;

● String字符串是常量,字符串的值一旦被创建,就不能被改变;

● String字符串缓冲区支持可变字符串;

● String对象是不可变的,它们是可以被共享的。

三. String的不可变性

在学习了上面的这些核心源码之后,接下来,我们可以通过一个案例来实践验证一番,看看String字符串的内容到底能不能改变。这里有个代码案例,如下图所示:

在上述的案例结果中,大家可以看出,s的内容竟然发生了改变?!但我们不是一直说String是不可变的吗?这是咋回事?大家先别急,我们继续往下看。

要想弄明白这个问题,我们首先得知道一个知识点:引用和值的区别!

在上面的代码中,我们先是创建了一个 "yiyige" 为内容的字符串引用s,如下图:

 s其实先是指向了value对象,而value对象又指向了存储 "y,i,y,i,g,e" 字符的字符数组。但因为value被final修饰,所以value的值不可被更改。因此,上面代码中改变的其实是s的引用指向,而不是改变了String对象的值!

换句话说,上面实例中s的值,其实只是value的引用地址,并不是String的内容本身。当我们执行 s = "yyg" 语句时,Java会创建一个新的字面量对象 "yyg",而原来的 "yiyige" 字面量对象其实依然存在于内存的intern缓存池中。

在这里,String对象的改变,实际上是通过内存地址的“断开-连接”变化来完成的。在这个过程中,原字符串中的内容并没有发生任何的改变。String s = "yiyige" 和 s = "yyg"这两行代码,实质上是开辟了2个内存空间,s只是由原来指向 "yiyige" 变为指向 "yyg" 而已,而其原来的字符串内容,是没有发生改变的,如下图所示。

因此,我们在以后的开发中,如果要经常修改字符串的内容,请尽量少用String!因为如果字符串的指向经常的“断开-连接”,就会大大降低性能,我建议大家使用StringBuilder 或 StringBuffer 进行替换。

我们继续把上面的代码深入地分析一下。在Java中,因为数组也是对象, 所以value中存储的也只是一个引用,它指向一个真正的数组对象。在执行了String s = “yiyige”; 这句代码之后,真正的内存布局应该是下图这样的:

因为value是String封装的字符数组,value中所有的字符都属于String这个对象。而由于value是private的,没有提供setValue等公共方法来修改这个value值,所以我们在String类的外部是无法修改value值的,也就是说字符串一旦初始化就不能再被修改。

此外,value变量是final修饰的,也就是说在String类内部,一旦这个值初始化了,value这个变量所引用的地址就不会改变了,即一直引用同一个对象。正是基于这一层,我们才说String对象是不可变的对象。

所以String的不可变,其实是指value在栈中的引用地址不可变,而不是说常量池中value字符数组里的数据元素不可变。也就是说,value所引用的数组对象里的内容,其实是可以发生改变的。

那么我们又如何改变它呢?这就要通过反射来消除String类对象的不可变性啦!

四. String真的不可变吗?

在上述内容中,我们重点给大家解释了String字符串的可变性。现在大家应该已经知道了,String字符串的内容其实是可变的,不可改变的只是String字符串的对象地址。那么我们到底该怎么让String字符串的内容发生改变呢?在上述我们给大家提到了反射,接下来我们就来看看如何通过反射改变String字符串的内容吧。代码案例如下所示:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

try {

    String str = "yyg";

    System.out.println("str=" + str + ", 唯一性hash值=" + System.identityHashCode(str));

    Class stringClass = str.getClass();

    //获取String类中的value属性

    Field field = stringClass.getDeclaredField("value");

    //设置私有成员的可访问性,进行暴力反射

    field.setAccessible(true);

    //获取value数组中的内容

    char[] value = (char[]) field.get(str);

    System.out.println("value=" + Arrays.toString(value));

    value[1] = 'z';

    System.out.println("str=" + str + ", 唯一性hash值=" + System.identityHashCode(str));

catch (NoSuchFieldException | IllegalAccessException e) {

    e.printStackTrace();

}

执行结果如下图所示:

  

从上面的结果中我们可以看到,String字符串的字符数组,通过反射进行修改后,字符串的“内容”真的发生了变化!

并且我们又利用底层的java.lang.System#identityHashCode()方法(不管是否重写了hashCode方法),来获取到了该字符串对象的唯一哈希值,该方法获取的hash值与hashCode()方法是一样的。

从结果中,我们可以看到两个字符串的唯一hash值是一样的,这就证明字符串的引用地址没有发生改变。

所以这就说明,我们并不是像之前那样创建了一个新的String字符串,而是真的改变了原有String的内容。

这个代码案例进一步证明了我们上面的结论:String字符串的不可变,指的其实是value对象在栈中的引用地址不可变,而不是说常量池中value里的数据元素不可变!简单地说,就是String字符串的内容其实是可以改变的,不能改表的是它的对象地址而已。

 所以这也就是我们上述所说的final的作用之一:final关键词修饰的引用数据类型的变量,其值为地址值,地址值不能改变,但是地址内的数据对象可以被改变!

五. 总结

至此,我们就把今天的面试题分析完了,现在你明白了吗?最后我再来给大家总结一下今天的重点内容吧:

1.  为什么要用final修饰java中的String类呢?

核心:因为它确保了字符串的安全性和可靠性。

2.  java中的String真的不可变吗?

核心:String字符串的内容其实是可变的,但要通过特殊手段进行实现,不可改变的是String字符串对象的地址。

3.  如何消除String类对象的不可变性?

核心:利用反射来消除String类对象的不可变性。

4.  如果想要保证String的不可变要注意哪些?

● 首先,将 String 类声明为 final类型。这意味着String类是不可被继承的,防止程序员通过继承重写String类的某些方法,使得String类出现“可变的”的情况;

● 然后,重要的字符数组value属性,要被private 和 final修饰。它是String的底层数组,用于存贮字符串内容。又因为数组是引用类型,所以只能限制引用不被改变,也就是说数组元素的值是可以改变的,这在上面的案例中已经证明过了;

● 接着,所有修改的方法都返回新的字符串对象,保证修改时不会改变原始对象的引用;

● 最后,不同的字符串对象都可以指向缓存池中的同一个字符串字面量。

当然Java中的String类使用final修饰”这个概念非常重要,因为它确保了字符串的安全性和可靠性。但是我们也要清楚不可改变的只是它的地址,而不是它的内容,它的内容是可以利用反射来改变的!只不过在一般的描述中,大家都会说String内容不可改变,毕竟很多时候是不允许利用反射这种特殊的功能去进行这样的操作的。

相关文章:

Java中的String类真的不可变吗?

其实在Java中&#xff0c;String类被final修饰&#xff0c;主要是为了保证字符串的不可变性&#xff0c;进而保证了它的安全性。那么final到底是怎么保证字符串安全性的呢&#xff1f;接下来就让我们一起来看看吧。 一. final的作用 1. final关键词修饰的类不可以被其他类继…...

电脑重装了系统开不了机怎么办?

我们的电脑办公用久后也会出现故障问题&#xff0c;例如卡顿反应慢等等&#xff0c;这时候就要进行重装系统了&#xff0c;但是很多小伙伴重装系统后会出现开不了机的问题&#xff0c;其实我们比较常见的也就是电脑重装系统开不了机的情况。有很多小伙伴反映自己不知道应该怎么…...

SPOJ-NSUBSTR - Substrings(SAM求所有长度子串的最大出现次数)

NSUBSTR - Substrings 题面翻译 你得到了一个最多由 250000250000250000 个小写拉丁字母组成的字符串 SSS。定义 F(x)F(x)F(x) 为 SSS 的某些长度为 xxx 的子串在 SSS 中的最大出现次数。即 F(x)max{times(T)}F(x)max\{times(T)\}F(x)max{times(T)}&#xff0c;满足 TTT 是 S…...

Mariadb10.5基于同服务器多实例主从配置

本次部署环境&#xff1a;Centos8stream 本次部署mariadb版本&#xff1a; mariadb:10.5 本次部署方式&#xff1a;rpm包直接安装&#xff0c;并通过systemd直接托管 可以参考 /usr/lib/systemd/system/mariadb.service 该文件 # Multi instance version of mariadb. For i…...

linux 修改主机名称

1、hostname命令进行临时更改 如果只需要临时更改主机名&#xff0c;可以使用hostname命令&#xff1a; sudo hostname <new-hostname> 例如&#xff1a; 只需重新打开session终端&#xff0c;就能生效&#xff0c; 但是&#xff0c;重启计算机后会回到旧的主机名。…...

学校的地下网站(学校的地下网站1080P高清)

这个问题本身就提得有问题&#xff0c;为什么这么说&#xff0c;这是因为YouTube本身就不是一个视频网站或者说YouTube不是一个传统的视频网站&#xff01;&#xff01;&#xff01; YouTube能够一家独大&#xff0c;可不仅仅是因为有了Google 这个亲爹&#xff0c;还有一点&am…...

勒索病毒是什么?如何防勒索病毒

勒索病毒并不是某一个病毒&#xff0c;而是一类病毒的统称&#xff0c;主要以邮件、程序、木马、网页挂马的形式进行传播&#xff0c;利用各种加密算法对文件进行加密&#xff0c;被感染者一般无法解密&#xff0c;必须拿到解密的私钥才有可能破解。 已知最早的勒索软件出现于 …...

SpringBoot+VUE+Axios 【链接超时】 后端正常返回结果,前端却出现错误无法接收数据

一、错误原因及解决思路 错误提示表明前端发送的请求在默认的 2500ms 超时时间内没有得到服务器的响应&#xff0c;导致请求失败。尝试以下方法来解决这个问题&#xff1a; 增加请求超时时间&#xff1a;可以通过配置 Axios 请求对象的 timeout 属性来增加请求的超时时间&…...

【状态估计】基于增强数值稳定性的无迹卡尔曼滤波多机电力系统动态状态估计(Matlab代码实现)

&#x1f4a5;&#x1f4a5;&#x1f49e;&#x1f49e;欢迎来到本博客❤️❤️&#x1f4a5;&#x1f4a5; &#x1f3c6;博主优势&#xff1a;&#x1f31e;&#x1f31e;&#x1f31e;博客内容尽量做到思维缜密&#xff0c;逻辑清晰&#xff0c;为了方便读者。 ⛳️座右铭&a…...

快速排序的简单理解

详细描述 快速排序通过一趟排序将待排序列分割成独立的两部分&#xff0c;其中一部分序列的关键字均比另一部分序列的关键字小&#xff0c;则可分别对这两部分序列继续进行排序&#xff0c;以达到整个序列有序的目的。 快速排序详细的执行步骤如下&#xff1a; 从序列中挑出…...

短视频多平台发布软件功能详解

随着移动互联网的普及和短视频的兴起&#xff0c;短视频发布软件越来越受到人们的关注。短视频发布软件除了常规的短视频发布功能&#xff0c;还拥有智能创作、帐号绑定、短视频一键发布、视频任务管理和数据统计等一系列实用功能。下面我们将分步骤详细介绍一下这些功能。   …...

谷歌人机验证Google reCAPTCHA

reCAPTCHA是Google公司推出的一项验证服务&#xff0c;使用十分方便快捷&#xff0c;在国外许多网站上均有使用。它与许多其他的人机验证方式不同&#xff0c;它极少需要用户进行各种识图验证。 它的使用方式如下如所示&#xff0c;只需勾选复选框即可通过人机验证。 虽然简单…...

VB+ACCESS电脑销售系统的设计与实现

为了使此系统简单易学易用、功能强大、软件费用支出低、见效快等特点&#xff0c;我们选择Visual Basic6.0开发此系统。Visual Basic6.0起代码有效率以达到Visual c的水平。在面向对象程序设计方面&#xff0c;Visual Basic6.0全面支持面向对你程序设计包括数据抽象、封装、对象…...

嵌入式开发:硬件和软件越来越接近

从前&#xff0c;硬件和软件工程师大多生活在自己的世界里。硬件团队设计了芯片&#xff0c;调试了从铸造厂返回的第一批样本&#xff0c;让软件团队测试他们的代码。随着虚拟平台和其他可执行模型变得越来越普遍&#xff0c;软件团队可以在芯片制造之前开始&#xff0c;有时甚…...

亲测:腾讯云轻量应用服务器性能如何?

腾讯云轻量应用服务器性能评测&#xff0c;轻量服务器CPU主频、处理器型号、公网带宽、月流量、Ping值测速、磁盘IO读写及使用限制&#xff0c;轻量应用服务器CPU内存性能和标准型云服务器CVM处于同一水准&#xff0c;所以大家不要担心轻量应用服务器的性能&#xff0c;腾讯云百…...

编程语言,TIOBE 4 月榜单:黑马出现了

TIOBE 4 月榜单已经发布了&#xff0c;一起来看看这个月编程语言排行榜有什么变化吧&#xff01; C 发展依旧迅猛 在本月榜单中&#xff0c;TOP 20 的变动不大&#xff0c;Python、C、Java 、 C 和C#依然占据前五。甚至排名顺序都和上个月一样没有变动。 同时&#xff0c;Rus…...

基于DSP+FPGA的机载雷达伺服控制系统(二)电源仿真

板级电源分配网络的分析与仿真在硬件电路设计中&#xff0c;电源系统的设计是关键步骤之一&#xff0c;良好的电源系统为电路板 上各种信号的传输提供了保障。本章将研究电源完整性的相关问题&#xff0c;并提出一系列改 进电源质量的措施。 3.1 电源完整性 电源完整性&#xf…...

SpringBoot整合Redis、以及缓存穿透、缓存雪崩、缓存击穿的理解分布式情况下如何添加分布式锁 【续篇】

文章目录前言1、分布式情况下如何加锁2、具体实现过程3、测试3.1 一个服务按照多个端口同时启动3.2 使用jmeter进行压测前言 上一篇实现了单体应用下如何上锁,这一篇主要说明如何在分布式场景下上锁 上一篇地址:加锁 1、分布式情况下如何加锁 需要注意的点是: 在上锁和释放…...

优漫动游告诉你:平面设计适合你吗?

优漫动游告诉你&#xff1a;平面设计适合你吗&#xff1f; 什么样的同学可以适应平面设计这份工作呢&#xff1f;   略微有美术基础&#xff0c;当然功底越深越加分。   2.对色彩、形状、结构有一定的接纳力。   3.对图案、人像、字体等因素有审美辨别的能力…...

在Vue中,为什么从 props 中解构变量之后再watch它,无法检测到它的变化?

例如下面这段代码&#xff0c;msg无法被watch import { watch } from vue;export default {props: {msg: String},setup(props) {// 从 props 中解构 msgconst { msg } props;watch(() > msg,(newVal, oldVal) > {console.log(newVal, newVal);console.log(oldVal, old…...

3.3.1_1 检错编码(奇偶校验码)

从这节课开始&#xff0c;我们会探讨数据链路层的差错控制功能&#xff0c;差错控制功能的主要目标是要发现并且解决一个帧内部的位错误&#xff0c;我们需要使用特殊的编码技术去发现帧内部的位错误&#xff0c;当我们发现位错误之后&#xff0c;通常来说有两种解决方案。第一…...

8k长序列建模,蛋白质语言模型Prot42仅利用目标蛋白序列即可生成高亲和力结合剂

蛋白质结合剂&#xff08;如抗体、抑制肽&#xff09;在疾病诊断、成像分析及靶向药物递送等关键场景中发挥着不可替代的作用。传统上&#xff0c;高特异性蛋白质结合剂的开发高度依赖噬菌体展示、定向进化等实验技术&#xff0c;但这类方法普遍面临资源消耗巨大、研发周期冗长…...

Python爬虫实战:研究feedparser库相关技术

1. 引言 1.1 研究背景与意义 在当今信息爆炸的时代,互联网上存在着海量的信息资源。RSS(Really Simple Syndication)作为一种标准化的信息聚合技术,被广泛用于网站内容的发布和订阅。通过 RSS,用户可以方便地获取网站更新的内容,而无需频繁访问各个网站。 然而,互联网…...

uniapp手机号一键登录保姆级教程(包含前端和后端)

目录 前置条件创建uniapp项目并关联uniClound云空间开启一键登录模块并开通一键登录服务编写云函数并上传部署获取手机号流程(第一种) 前端直接调用云函数获取手机号&#xff08;第三种&#xff09;后台调用云函数获取手机号 错误码常见问题 前置条件 手机安装有sim卡手机开启…...

iview框架主题色的应用

1.下载 less要使用3.0.0以下的版本 npm install less2.7.3 npm install less-loader4.0.52./src/config/theme.js文件 module.exports {yellow: {theme-color: #FDCE04},blue: {theme-color: #547CE7} }在sass中使用theme配置的颜色主题&#xff0c;无需引入&#xff0c;直接可…...

【UE5 C++】通过文件对话框获取选择文件的路径

目录 效果 步骤 源码 效果 步骤 1. 在“xxx.Build.cs”中添加需要使用的模块 &#xff0c;这里主要使用“DesktopPlatform”模块 2. 添加后闭UE编辑器&#xff0c;右键点击 .uproject 文件&#xff0c;选择 "Generate Visual Studio project files"&#xff0c;重…...

Django RBAC项目后端实战 - 03 DRF权限控制实现

项目背景 在上一篇文章中&#xff0c;我们完成了JWT认证系统的集成。本篇文章将实现基于Redis的RBAC权限控制系统&#xff0c;为系统提供细粒度的权限控制。 开发目标 实现基于Redis的权限缓存机制开发DRF权限控制类实现权限管理API配置权限白名单 前置配置 在开始开发权限…...

CTF show 数学不及格

拿到题目先查一下壳&#xff0c;看一下信息 发现是一个ELF文件&#xff0c;64位的 ​ 用IDA Pro 64 打开这个文件 ​ 然后点击F5进行伪代码转换 可以看到有五个if判断&#xff0c;第一个argc ! 5这个判断并没有起太大作用&#xff0c;主要是下面四个if判断 ​ 根据题目…...

用鸿蒙HarmonyOS5实现国际象棋小游戏的过程

下面是一个基于鸿蒙OS (HarmonyOS) 的国际象棋小游戏的完整实现代码&#xff0c;使用Java语言和鸿蒙的Ability框架。 1. 项目结构 /src/main/java/com/example/chess/├── MainAbilitySlice.java // 主界面逻辑├── ChessView.java // 游戏视图和逻辑├── …...

第2课 SiC MOSFET与 Si IGBT 静态特性对比

2.1 输出特性对比 2.2 转移特性对比 2.1 输出特性对比 器件的输出特性描述了当温度和栅源电压(栅射电压)为某一具体数值时,漏极电流(集电极电流...