String类是不可变类,一个String对象被创建以后,包含这个对象中的字符串序列是不可改变的。与其问String为什么是不可变的,还不如问String类是如何实现其对象不可变的。

什么是不可变对象?

如果一个对象它被创建后,状态不能改变,则这个对象被认为是不可变的。

String是如何实现其对象不可变?

首先需要补充一个容易混淆的知识点:当使用final修饰基本类型变量时,不能对基本类型变量重新赋值,因此基本类型变量不能被改变。但对于引用类型变量而言,它保存的仅仅是一个引用,final只保证这个引用变量所引用的地址不会改变,即一直引用同一个对象,但这个对象完全可以发生改变。例如某个指向数组的final引用,它必须从此至终指向初始化时指向的数组,但是这个数组的内容完全可以改变。

我们来看一下String类的两个主要成员变量,其中value指向的是一个字符串数组,字符串中的字符就是用这个value变量存储起来的,并且用final修饰,也就是说value一旦赋予初始值之后,value指向的地址就不能再改变了。虽然value指向的数组是可以改变的,但是String也没有提供相应的方法让我们去修改value指向的数组的元素。然而在StringBuilder中是提供了响应的方法让我们去修改value指向的数组的元素,这也是StringBuilder的字符串序列可变的原因。

  /** The value is used for character storage. */private final char value[];/** Cache the hash code for the string */private int hash; // Default to 0

有一些看起来String对象可变的幻觉?

String中提供了一些看似可以改变String对象的方法,但实际上它们已经是指向了一个新建的对象。

程序例子:

public static void main(String[] args) {String str1 = "hello";// 打印str1的内存地址System.out.println("str1的内存地址:" + System.identityHashCode(str1));String str2 = "world";str1 += str2;// str1的内存地址已经改变了System.out.println("执行+=后str1的内存地址:" + System.identityHashCode(str1));System.out.println("拼接之后str1的值:" + str1);String str3 = "123";// 创建一个新的对象来保存拼接之后的值String str4 = str3.concat("456");// concat操作不会改变原来str3的值System.out.println("str3的值:" + str3);System.out.println("str4的值:" + str4);String str5 = "ABC";// replace操作不会改变原来str6的值String str6 = str5.replace("A", "B");System.out.println("str5的值:" + str5);System.out.println("str6的值:" + str6);}

运行结果:

str1的内存地址:1922154895
执行+=后str1的内存地址:883049899
拼接之后str1的值:helloworld
str3的值:123
str4的值:123456
str5的值:ABC
str6的值:BBC

程序分析:

str1+=str2实际上是执行了str1=(new StringBuilder()).append(str2).toString();前后实际额外产生了一个StringBuilder与一个helloworld的字符串常量。str1执行+=前后内存的示意图如下所示:

上面使用了String类的concat与replace方法,执行这两个操作不会对原来的对象产生影响,他们会返回一个全新的对象。我们可以来看一下这两个方法的源码。

concat方法源码:

public String concat(String str) {int otherLen = str.length();if (otherLen == 0) {return this;}int len = value.length;char buf[] = Arrays.copyOf(value, len + otherLen);str.getChars(buf, len);return new String(buf, true);}

replace方法源码:

public String replace(char oldChar, char newChar) {if (oldChar != newChar) {int len = value.length;int i = -1;char[] val = value; /* avoid getfield opcode */while (++i < len) {if (val[i] == oldChar) {break;}}if (i < len) {char buf[] = new char[len];for (int j = 0; j < i; j++) {buf[j] = val[j];}while (i < len) {char c = val[i];buf[i] = (c == oldChar) ? newChar : c;i++;}return new String(buf, true);}}return this;}

String对象真的不可变吗?

虽然value是final修饰的,只是说明value不能再重新指向其他的引用。但是value指向的数组可以改变,一般情况下我们是没有办法访问到这个value指向的数组的元素。But,反射,对,反射可以,牛逼吧。可以反射出String对象中的value属性, 进而改变通过获得的value引用改变数组的结构。show you the code!

public static void main(String[] args) throws Exception {String str = "Hello World";System.out.println("修改前的str:" + str);System.out.println("修改前的str的内存地址" + System.identityHashCode(str));// 获取String类中的value字段Field valueField = String.class.getDeclaredField("value");// 改变value属性的访问权限valueField.setAccessible(true);// 获取str对象上value属性的值char[] value = (char[]) valueField.get(str);// 改变value所引用的数组中的字符value[3] = '?';System.out.println("修改后的str:" + str);System.out.println("修改前的str的内存地址" + System.identityHashCode(str));}

运行结果:

修改前的str:Hello World
修改前的str的内存地址1922154895
修改后的str:Hel?o World
修改前的str的内存地址1922154895

可以看到str的字符串序列已经被改变了,但是str的内存地址还是没有改变。有疑问?在下方留言哦。

Java中的String为什么是不可变的?相关推荐

  1. Java 中的 String 真的是不可变的吗

    转载自   Java 中的 String 真的是不可变的吗 我们都知道 Java 中的 String 类的设计是不可变的,来看下 String 类的源码. public final class Str ...

  2. Java中的String为什么是不可变的? -- String源码分析

    什么是不可变对象? 众所周知, 在Java中, String类是不可变的.那么到底什么是不可变的对象呢? 可以这样认为:如果一个对象,在它创建完成之后,不能再改变它的状态,那么这个对象就是不可变的.不 ...

  3. Java中的String类为什么不可变

    1.什么是不可变? java角度来讲就是说成final的. String不可变如下图: 假设给字符串s赋值为abcd,第二次重新赋值为abcdef,这时候并不是在原内存地址上修改数据,而是重新指向一个 ...

  4. java 字符串是对象吗_解析Java中的String对象的数据类型

    解析Java中的String对象的数据类型 2007-06-06 eNet&Ciweek 1. 首先String不属于8种基本数据类型,String是一个对象. 因为对象的默认值是null,所 ...

  5. Java中关于String类型的10个问题

    转载自   Java中关于String类型的10个问题 1. 如何比较两个字符串?用"="还是equals 简单来说,"=="是用来检测俩引用是不是指向内存中的 ...

  6. c++中string插入一个字符_Java内存管理-探索Java中字符串String(十二)

    做一个积极的人 编码.改bug.提升自己 我有一个乐园,面向编程,春暖花开! 一.初识String类 首先JDK API的介绍: public final class String extends O ...

  7. Java 中的 String、StringBuilder、StringBuffer 的区别

    目录 一.是什么? 二.区别是? 1. 运行速度(执行速度) 2. 线程安全 三.小结 四.加餐 一.是什么? String 不可变字符序列 String 是字符串常量,其对象一旦创建之后该对象是不可 ...

  8. 深入理解Java中的String(原地址https://www.cnblogs.com/xiaoxi/p/6036701.html)

    深入理解Java中的String 一.String类 想要了解一个类,最好的办法就是看这个类的实现源代码,来看一下String类的源码: public final class Stringimplem ...

  9. Java中的String,StringBuilder,StringBuffer三者的区别

    最近在学习Java的时候,遇到了这样一个问题,就是String,StringBuilder以及StringBuffer这三个类之间有什么区别呢,自己从网上搜索了一些资料,有所了解了之后在这里整理一下, ...

最新文章

  1. 解决每次git pull需要不用输入用户名信息
  2. ​HealthKit开发快速入门教程大学霸内部教程
  3. 6410调试LCD屏AT050TN22遇到的问题
  4. php 数组合并_PHP数组常用函数分类整理
  5. (转)Windows 批处理实现 定时打开IE 延时一段时间后 关闭IE
  6. Supervisor管理hhvm进程
  7. 强大的Mockito测试框架
  8. Spark源码系列(一)spark-submit提交作业过程
  9. 随手写了一段C++访问LDAP, 并且获取sid的代码
  10. 站内信 java_站内信的实现思路表的设计
  11. Ubuntu 16.04中为Chromium、Chrome、Firefox安装Flash播放器插件
  12. C:1094统计元音(函数专题)
  13. 中职学校计算机应用基础学什么,浅谈中职学校《计算机应用基础》课程改革的一点建议...
  14. [Unity] GPU动画实现(三)——材质合并
  15. 通配符SSL证书的作用有哪些
  16. iOS马甲包系统性全流程把控
  17. python readfile 管道_使用win32的正确方法是什么文件.ReadFile从管道中获取输出?
  18. EOJ1270-Arbitrage(套利交易)
  19. wordpress空间上传新网站出现的问题
  20. 这两个软件让你实现识图查车牌号

热门文章

  1. 伪类、伪元素及五星好评css实现
  2. 三丰云永久免费云服务器
  3. 如何写一个魔方二维动态还原MATLAB仿真程序
  4. 项目经理年终总结|一个高层项目管理者的年终反思
  5. Android夜间模式最佳实践
  6. 最全的SQL练习题(做完你就是高手)
  7. Redis基于内存非关系型数据库
  8. 【清华集训2014】玛里苟斯
  9. 如何通过云终端,让一台电脑能多台使用?
  10. 在线浏览pdf doc html,前端实现在线预览pdf、word、xls、ppt等文件