Loading [MathJax]/jax/output/CommonHTML/config.js
前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
社区首页 >专栏 >STRING对象不可变的原理

STRING对象不可变的原理

作者头像
用户4283147
发布于 2022-10-08 04:42:26
发布于 2022-10-08 04:42:26
73310
代码可运行
举报
文章被收录于专栏:对线JAVA面试对线JAVA面试
运行总次数:0
代码可运行

ps:主要是其内部使用了private final修饰成员,如果想修改,需要使用到反射技术

string方法的修改效果是通过新建StringBuilder来实现的,更老的版本使用StringBuffer,后者是线程安全版本,但效率较低

什么是不可变对象?

众所周知, 在Java中, String类是不可变的。那么到底什么是不可变的对象呢?可以这样认为:如果一个对象,在它创建完成之后,不能再改变它的状态,那么这个对象就是不可变的。不能改变状态的意思是,不能改变对象内的成员变量,包括基本数据类型的值不能改变,引用类型的变量不能指向其他的对象,引用类型指向的对象的状态也不能改变。

区分对象和对象的引用

对于Java初学者, 对于String是不可变对象总是存有疑惑。看下面代码:

String s = "ABCabc";

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
System.out.println("s = " + s);  
 
s = "123456";  
System.out.println("s = " + s);  

打印结果为:

s = ABCabc s = 123456

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

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

Java和C++的一个不同点是, 在Java中不可能直接操作对象本身,所有的对象都由一个引用指向,必须通过这个引用才能访问对象本身,包括获取成员变量的值,改变对象的成员变量,调用对象的方法等。而在C++中存在引用,对象和指针三个东西,这三个东西都可以访问对象。其实,Java中的引用和C++中的指针在概念上是相似的,他们都是存放的对象在内存中的地址值,只是在Java中,引用丧失了部分灵活性,比如Java中的引用不能像C++中的指针那样进行加减运算。

为什么String对象是不可变的?

要理解String的不可变性,首先看一下String类中都有哪些成员变量。在JDK1.6中,String的成员变量有以下几个:

public final class String

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
 implements java.io.Serializable, Comparable<String>, CharSequence  
{  
 /** The value is used for character storage. */ 
 private final char value[];  
 
 /** The offset is the first index of the storage that is used. */ 
 private final int offset;  
 
 /** The count is the number of characters in the String. */ 
 private final int count;  
 
 /** Cache the hash code for the string */ 
 private int hash; // Default to 0 

在JDK1.7中,String类做了一些改动,主要是改变了substring方法执行时的行为,这和本文的主题不相关。JDK1.7中String类的主要成员变量就剩下了两个:

public final class String

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
 implements java.io.Serializable, Comparable<String>, CharSequence {  
 /** The value is used for character storage. */ 
 private final char value[];  
 
 /** Cache the hash code for the string */ 
 private int hash; // Default to 0 

由以上的代码可以看出, 在Java中String类其实就是对字符数组的封装。JDK6中, value是String封装的数组,offset是String在这个value数组中的起始位置,count是String所占的字符的个数。在JDK7中,只有一个value变量,也就是value中的所有字符都是属于String这个对象的。这个改变不影响本文的讨论。除此之外还有一个hash成员变量,是该String对象的哈希值的缓存,这个成员变量也和本文的讨论无关。在Java中,数组也是对象(可以参考我之前的文章 java中数组的特性)。所以value也只是一个引用,它指向一个真正的数组对象。其实执行了String s = “ABCabc”; 这句代码之后,真正的内存布局应该是这样的:

value,offset和count这三个变量都是private的,并且没有提供setValue, setOffset和setCount等公共方法来修改这些值,所以在String类的外部无法修改String。也就是说一旦初始化就不能修改, 并且在String类的外部不能访问这三个成员。此外,value,offset和count这三个变量都是final的, 也就是说在String类内部,一旦这三个值初始化了, 也不能被改变。所以可以认为String对象是不可变的了。

那么在String中,明明存在一些方法,调用他们可以得到改变后的值。这些方法包括substring, replace, replaceAll, toLowerCase等。例如如下代码:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
String a = "ABCabc";  
System.out.println("a = " + a);  
a = a.replace('A', 'a');  
System.out.println("a = " + a);  

打印结果为:

a = ABCabc a = aBCabc

那么a的值看似改变了,其实也是同样的误区。再次说明, a只是一个引用, 不是真正的字符串对象,在调用a.replace('A', 'a')时, 方法内部创建了一个新的String对象,并把这个心的对象重新赋给了引用a。String中replace方法的源码可以说明问题:

读者可以自己查看其他方法,都是在方法内部重新创建新的String对象,并且返回这个新的对象,原来的对象是不会被改变的。这也是为什么像replace, substring,toLowerCase等方法都存在返回值的原因。也是为什么像下面这样调用不会改变对象的值:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
String ss = "123456";  
 
System.out.println("ss = " + ss);  
 
ss.replace('1', '0');  
 
System.out.println("ss = " + ss);  

打印结果:

ss = 123456 ss = 123456

String对象真的不可变吗?

从上文可知String的成员变量是private final 的,也就是初始化之后不可改变。那么在这几个成员中, value比较特殊,因为他是一个引用变量,而不是真正的对象。value是final修饰的,也就是说final不能再指向其他数组对象,那么我能改变value指向的数组吗?比如将数组中的某个位置上的字符变为下划线“_”。至少在我们自己写的普通代码中不能够做到,因为我们根本不能够访问到这个value引用,更不能通过这个引用去修改数组。

那么用什么方式可以访问私有成员呢?没错,用反射, 可以反射出String对象中的value属性, 进而改变通过获得的value引用改变数组的结构。下面是实例代码:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
public static void testReflection() throws Exception {  
 
 //创建字符串"Hello World", 并赋给引用s 
    String s = "Hello World";   
 
    System.out.println("s = " + s); //Hello World 
 
 //获取String类中的value字段 
    Field valueFieldOfString = String.class.getDeclaredField("value");  
 
 //改变value属性的访问权限 
    valueFieldOfString.setAccessible(true);  
 
 //获取s对象上的value属性的值 
 char[] value = (char[]) valueFieldOfString.get(s);  
 
 //改变value所引用的数组中的第5个字符 
    value[5] = '_';  
 
    System.out.println("s = " + s);  //Hello_World 
}  

打印结果为:

s = Hello World s = Hello_World

在这个过程中,s始终引用的同一个String对象,但是再反射前后,这个String对象发生了变化, 也就是说,通过反射是可以修改所谓的“不可变”对象的。但是一般我们不这么做。这个反射的实例还可以说明一个问题:如果一个对象,他组合的其他对象的状态是可以改变的,那么这个对象很可能不是不可变对象。例如一个Car对象,它组合了一个Wheel对象,虽然这个Wheel对象声明成了private final 的,但是这个Wheel对象内部的状态可以改变, 那么就不能很好的保证Car对象不可变。

本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2021-06-15,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 对线JAVA面试 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
1 条评论
热度
最新
非常精彩,请问官方为什么要设计String为final
非常精彩,请问官方为什么要设计String为final
回复回复点赞举报
推荐阅读
编辑精选文章
换一批
工作三年,小胖连 String 源码都没读过?真的菜!
String 类相信大家都不陌生,它是引用类型,同时也是工作中用的最多的一个类。那它到底是怎么实现的呢?我们看源码:
JavaFish
2021/02/03
4880
工作三年,小胖连 String 源码都没读过?真的菜!
深入理解String,StringBuilder,StringBuffer
从上面的三个方法可以看出,无论是subString、concat还是replace操作都不是在原有的字符串上进行的,而是重新生成了一个新的字符串对象。也就是说进行这些操作后,最原始的字符串并没有被改变。
yesr
2019/03/20
5220
深入理解String,StringBuilder,StringBuffer
StringBuilder类解析
参考链接: StringBuilder类 在解析StringBuilder类之前,我们先对String类做一个简单的分析。不过分析之前先想一个问题,为什么有好好的String类不用,要专用String
用户7886150
2021/02/02
4080
你真的懂Java中的String、StringBuilder和StringBuffer吗?
相信String这个类是Java中使用得最频繁的类之一,并且又是各大公司面试喜欢问到的地方,今天就来和大家一起学习一下String、StringBuilder和StringBuffer这几个类,分析它们的异同点以及了解各个类适用的场景。下面是本文的目录大纲:
Bug开发工程师
2018/07/23
7240
你真的懂Java中的String、StringBuilder和StringBuffer吗?
【Java基本功】一文读懂String及其包装类的实现原理
String作为Java中最常用的引用类型,相对来说基本上都比较熟悉,无论在平时的编码过程中还是在笔试面试中,String都很受到青睐,然而,在使用String过程中,又有较多需要注意的细节之处。
Java技术江湖
2019/09/25
4630
Java中String部分源码解析
既然String是不可变的,好像内部还有很多substring, replace, replaceAll这些操作的方法。好像都是对String对象改变了,解释起来也很简单,我们每次的replace这些操作,其实就是在堆内存中创建了一个新的对象。然后我们的value指向不同的对象罢了。
宇宙之一粟
2020/10/26
3390
Java不可变类机制
不可变类(Immutable Class):所谓的不可变类是指这个类的实例一旦创建完成后,就不能改变其成员变量值。如JDK内部自带的很多不可变类:Interger、Long和String等。
用户7886150
2021/02/26
9640
JAVA不可变类(immutable)机制与String的不可变性
不可变类:所谓的不可变类是指这个类的实例一旦创建完成后,就不能改变其成员变量值。如JDK内部自带的很多不可变类:Interger、Long和String等。 可变类:相对于不可变类,可变类创建实例后可以改变其成员变量值,开发中创建的大部分类都属于可变类。
lyb-geek
2018/12/26
1.7K0
String、StringBuilder、StringBuffer区别;String底层详解,实例化、拼接、比较;String为什么不可变
String是Java中的一个内置类,Immutable不可变,即一旦创建String对象,它的值就不能被更改。对String对象的replace、subString、toLowerCase等操作都会返回一个新String对象,故每次操作String时 性能较低、浪费内存空间
寻求出路的程序媛
2024/04/21
2590
String、StringBuilder、StringBuffer区别;String底层详解,实例化、拼接、比较;String为什么不可变
多线程----Immutable VS Mutable (可变与不可变)
Immutable是什么意思?不变的、不发生改变的意思。在JDK中有很多的类被设计成不可变的,举个大家经常用到的类java.lang.String,String类被设计成不可变。String所表示的字符串的内容绝对不会发生变化。因此,在多线程的情况下,String类无需进行互斥处理,不用给方法进行synchronized或者lock等操作,进行上锁、争抢锁、解锁等流程也是有一定性能损耗的。因此,若能合理的利用Immutable,一定对性能的提升有很大帮助。
令仔很忙
2019/02/25
6130
必知必会:String不可变的3个好处
大家好,欢迎来到程序视点!我是小二哥。今天我们来来聊聊String类型对象不可变的问题。
程序视点
2023/11/03
3680
必知必会:String不可变的3个好处
最最最常见的Java面试题总结——第二周
String类中使用字符数组:private final char value[]保存字符串,所以String对象是不可变的。StringBuilder与StringBuffer都继承自AbstractStringBuilder类,在AbstractStringBuilder中也是使用字符数组保存字符串,char[]value,这两种对象都是可变的。
用户2164320
2018/08/13
5580
执行了String s = “ABCabc”之后,内存布局是什么样的?
value是String封装的数组,value中的所有字符都是属于String这个对象的。由于value是private的,并且没有提供setValue等公共方法来修改这些值,所以在String类的外部无法修改String。也就是说一旦初始化就不能修改。此外,value变量是final的, 也就是说在String类内部,一旦这个值初始化了,value引用类型变量所引用的地址不会改变,即一直引用同一个对象。所以可以说String对象是不可变对象。但其实value所引用对象的内容完全可以发生改变(反射消除String类对象的不可变特性)。
用户7365393
2021/10/07
3410
String类的不可变性
来源 | 简书| 作者 | 指尖上的榴莲 一.原理(为什么说String类是不可变的) 1.什么是不可变对象 如果一个对象在创建之后就不能再改变它的状态,那么这个对象是不可变的(Immutable)。
Tanyboye
2018/07/02
6050
Java String 理解
我们经常会面对一个问题,String 是最基本的数据类型吗? String 是值类型还是引用类型? 首先我们来回答第一个问题 String 是最基本的数据类型吗? 不是。Java中的基本数据类型只有
Tim在路上
2020/08/05
5030
String类不可变分析以及普通不可变类
要了解String类创建的实例为什么不可变,首先要知道final关键字的作用:final的意思是“最终,最后”。
名字是乱打的
2021/12/22
6450
String类不可变分析以及普通不可变类
为什么String是不可变
区分对象和对象的引用 对于Java初学者, 对于String是不可变对象总是存有疑惑。看下面代码: String s = "ABCabc"; System.out.println("s = " + s);
用户9131103
2023/07/17
1660
在java中String类为什么要设计成final?Java面试常见问题
其实在Java中,String类被final修饰,主要是为了保证字符串的不可变性,进而保证了它的安全性。那么final到底是怎么保证字符串安全性的呢?接下来就让我们一起来看看吧。
IT千锋教育
2023/05/24
4260
在java中String类为什么要设计成final?Java面试常见问题
Java String类源码阅读笔记
本文基于jdk1.8 String类可谓是我们开发中使用最多的一个类了。对于它的了解,仅仅限于API的了解是不够的,必须对它的源码进行一定的学习。
三分恶
2020/09/22
4770
Java String类源码阅读笔记
Java常用类(二)String类详解
前言   在我们开发中经常会用到很多的常用的工具类,这里做一个总结。他们有很多的方法都是我们经常要用到的。所以我们一定要把它好好的掌握起来! 一、String简介 1.1、String(字符串常量)概述   在API中是这样描述:     String 类代表字符串。Java 程序中的所有字符串字面值(如 "abc" )都作为此类的实例实现。 字符串是常量;它们的值在创建之后不能更改。字符串缓冲区支持可变的字符串。因为 String 对象是不可变的,所以可以共享。   java.lang.String:
用户1195962
2018/01/18
1.1K0
Java常用类(二)String类详解
相关推荐
工作三年,小胖连 String 源码都没读过?真的菜!
更多 >
领券
社区富文本编辑器全新改版!诚邀体验~
全新交互,全新视觉,新增快捷键、悬浮工具栏、高亮块等功能并同时优化现有功能,全面提升创作效率和体验
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档
查看详情【社区公告】 技术创作特训营有奖征文