Java中的String类真的不可变吗?java面试常见问题

news2024/9/25 7:24:29

其实在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面试常见问题 - 可爱的小锋 - 博客园 (cnblogs.com)

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

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

相关文章

车载通讯USB开发,增强车内娱乐体验

车载通讯开发中使用的 USB 协议常见于车内娱乐系统、车载设备和汽车诊断工具等应用。USB&#xff08;Universal Serial Bus&#xff0c;通用串行总线&#xff09;是一种常见的数字通信接口标准&#xff0c;用于连接计算机、外部设备及其他电子设备之间的数据传输和通信。 USB …

Python面向对象编程到底怎么用才是最好的(两个小案例告诉你其中优势)

目录 前言案例一&#xff1a;图书管理系统案例二&#xff1a;汽车制造系统 总结 前言 大家好&#xff0c;我是辣条哥~ 当谈到Python编程语言时&#xff0c;面向对象编程&#xff08;Object-Oriented Programming&#xff0c;简称OOP&#xff09;是一个重要的概念。 OOP是一种…

Leetcode52 N 皇后 II

n 皇后问题 研究的是如何将 n 个皇后放置在 n n 的棋盘上&#xff0c;并且使皇后彼此之间不能相互攻击。 给你一个整数 n &#xff0c;返回 n 皇后问题 不同的解决方案的数量。 示例 1&#xff1a; 输入&#xff1a;n 4 输出&#xff1a;2 解释&#xff1a;如上图所示&…

uin-app项目实现pdf文件预览以及下载

由于项目需要&#xff0c;需要对于pdf格式的文件进行预览由用户进行选择性下载&#xff0c;查阅相关文档后方知针对于这种 pdf.js有奇效 一、下载 官网地址https://mozilla.github.io/pdf.js/getting_started/#download 文档下载解压成功后&#xff0c;按照这种格式放入uin-…

认识GBK编码和UTF-8编码

GBK编码和UTF-8编码是两种不同的字符编码方式&#xff1b; 1、主要区别如下&#xff1a; &#xff08;1&#xff09;字符集范围不同&#xff1a;GBK编码支持中文字符和日韩字符&#xff0c;而UTF-8编码支持全球范围内的字符&#xff1b; &#xff08;2&#xff09;编码方式不…

4.28 poll API介绍及代码编写

4.28 poll API介绍及代码编写 #include <poll.h> struct pollfd{int fd;//委托内核检测的文件描述符short events;//委托内核检测文件描述符的什么事件short revents;//文件描述符实际发生的事件 }; int poll(struct pollfd *fds,nfds_t nfds,int timeout);-参数&#x…

EDA云实证Vol.13:暴力堆机器之王——Calibre

Siemens的Calibre是业内权威的版图验证软件&#xff0c;被各大Foundry厂广泛认可。用户可以直接在Virtuoso界面集成Calibre接口&#xff0c;调用版图验证结果数据&#xff0c;使用起来极为方便。 今天&#xff0c;我们就来聊聊这款软件。 版图验证是芯片设计中非常重要的一环…

3.5.1MapReduce原理详解

单机程序计算流程 输入数据—>读取数据—>处理数据—>写入数据—>输出数据 Hadoop计算流程 input data&#xff1a;输入数据 InputFormat&#xff1a;对数据进行切分&#xff0c;格式化处理 map&#xff1a;将前面切分的数据做map处理(将数据进行分类&#xf…

怎么写一份好的接口文档?

目录 前言&#xff1a; 接口文档结构 参数说明 示例 错误码说明 语言基调通俗易懂 及时更新与维护 总结 前言&#xff1a; 写一份好的接口文档有助于开发者理解和使用你的API。&#xff0c; 编写一份优秀的接口文档会让软件开发中变得更加轻松&#xff0c;更有效率。…

一个大于6的偶数,能被两个质数相加得到

一个大于6的偶数,能被两个质数相加得到 1.描述 证明&#xff1a;一个大于6的偶数,能被两个质数相加得到 2.代码 先判断输入的数据是不是大于6的偶数 编写判断某个数是不是质数的方法 从中间两侧把数相加看看能不能得到对应的偶数两个奇数相加能够得到的值 public class Main…

linux入门之进程概念上(冯诺依曼系统,系统概念与定位,PCB,fork初识)

文章目录 目录 一、认识冯诺依曼系统 二、操作系统 1.概念 2.设计os的目的 3.定位 4.如何理解管理 三、系统调用和库函数 四、进程 1.基本概念 2.描述进程-PCB 3.组织进程 4.查看进程 5.通过系统调用获取进程标识符 6.通过系统调用创建进程-fork初识 6.1fork原理…

Spring(9) IOC容器中的单例Bean的线程安全问题

目录 1.知识点回顾2.线程安全3.总结 1.知识点回顾 在 Spring 中我们可以通过 Scope 注解来指定 Bean 的创建方式。 Servcie Scope("singleton") public class UserServiceImpl implements UserService {}Scope 注解的值有两种&#xff1a; singleton&#xff1a;B…

vue3使用高德地图实现点击获取经纬度

话不多说直接上干活 在此之前你需要有高德地图的 key&#xff0c;这个自己去申请即可 1&#xff0c;首先需要在终端安装 npm i amap/amap-jsapi-loader --save 2&#xff0c;准备一个容器 <template><div id"container"></div> </templat…

Redis+Lua脚本解决高并发情况下库存超卖的问题

文章目录 一、实现思路二、实现代码 一、实现思路 二、实现代码 order.lua脚本代码&#xff1a; -- 参数列表 local productIdStr ARGV[1] local productNameStr ARGV[2] local cartQuantityStr ARGV[3] local orderId ARGV[4] local userId ARGV[5] local orderDate A…

Android 控件颜色与实际不符「解决方案」

问题复现 背景色设置为 绿色&#xff0c;然而 Button 控件却显示 紫色 解决方案 这是由于 Theme 修改导致&#xff0c;只需要修改配置文件 themes.xml 中 parent 属性 即可 如果找不到该文件&#xff0c;先将工程结构展示改为 Project 即可 原配置&#xff1a; <style nam…

【网络智能化】网络杂谈(9)之如何做到网络管理智能化

涉及知识点 什么是网络管理智能化&#xff0c;基于专家系统的网络管理&#xff0c;基于智能 Agent 的网络管理&#xff0c;基于计算智能的宽带网络管理&#xff0c;深入了解网络管理智能化技术。 原创于&#xff1a;CSDN博主-《拄杖盲学轻声码》&#xff0c;更多内容可去其主页…

软件业务连续性管理的意义是什么?

软件业务连续性管理是指在软件开发和运营过程中&#xff0c;确保业务能够持续进行的管理方法和实践&#xff0c;它是一种综合性的管理方法&#xff0c;旨在确保在软件系统出现故障、灾难或其他不可预测的情况时&#xff0c;能够快速、有效地恢复业务&#xff0c;以最大程度地减…

【数据分享】全国县市2000-2021年教育、卫生和社会保障数据(excel\shp格式)

《中国县域统计年鉴》是一部全面反映我国县域社会经济发展状况的资料性年鉴&#xff0c;收录了上一年度全国2000多个县域单位的基本情况、综合经济、农业、工业、教育、卫生、社会保障等方面的资料。 之前基于《中国县域统计年鉴》我们分享了2000至2021年的综合经济数据和农业…

gitee删除已上传的废弃工程

第一步:打开Git Bash或者右击电脑桌面打开也可以做到 第二步:cd到指定的工程文件夹内 dir 看一下&#xff0c;下面的项目是否正确 第三步:输入命令 git rm -r 想要删除的项目名称 第四步&#xff1a;输入命令 git commit -m 备注 第五步&#xff1a;输入命令保存 git pu…

Day.1 LeetCode刷题练习(最长公共前缀 C/C++两种解法)

题目&#xff1a; 例子&#xff1a; 分析题目&#xff1a; 主要目的&#xff1a;求出各个字符串的公共前缀 思路&#xff08;本人解法&#xff09;&#xff1a; 用所给实例来看&#xff0c;不难看出我们可以直接以竖着对应来查看是否是公共前缀 &#xff0c; 这样就有了一…